可移植 C++ 编程¶
注意:本文档涵盖了需要为目标硬件环境构建和执行的代码。这适用于核心执行运行时,以及此仓库中的内核和后端实现。这些规则不一定适用于仅在开发主机上运行的代码,例如创作或构建工具。
ExecuTorch 运行时代码旨在可移植,应该为各种系统构建,从服务器到移动电话到 DSP,从 POSIX 到 Windows 到裸机环境。
这意味着它不能假设存在
文件
线程
异常
stdout
,stderr
printf()
,fprintf()
POSIX API 和一般概念
它也不能假设
64 位指针
给定整数类型的尺寸
char
的符号
为了将二进制大小保持在最小值,并严格控制内存分配,代码可能不会使用
malloc()
,free()
new
,delete
大多数
stdlibc++
类型;尤其是像string
和vector
这样管理自身内存的容器类型,或者像unique_ptr
和shared_ptr
这样的内存管理包装类型。
为了帮助降低复杂性,代码可能不依赖于除以下之外的任何外部依赖项
flatbuffers
(用于.pte
文件反序列化)flatcc
(用于事件跟踪序列化)核心 PyTorch(仅限 ATen 模式)
平台抽象层 (PAL)¶
为了避免假设目标系统的功能,ExecuTorch 运行时允许客户端覆盖其平台抽象层 (PAL) 中的低级函数,这些函数定义在 //executorch/runtime/platform/platform.h
中,以执行以下操作
获取当前时间戳
打印日志消息
使系统崩溃
内存分配¶
运行时代码不应使用 malloc()
或 new
,而应使用客户端提供的 MemoryManager
(//executorch/runtime/executor/memory_manager.h
) 来分配内存。
文件加载¶
客户端不应直接加载文件,而应提供包含已加载数据的缓冲区,或将其包装在 DataLoader
等类型中。
整数类型¶
ExecuTorch 运行时代码不应假设任何关于 int
、short
或 char
等原始类型的尺寸。例如,C++ 标准只保证 int
的宽度至少为 16 位。而 ARM 工具链将 char
视为无符号,而其他工具链通常将其视为有符号。
运行时 API 使用了一组更可预测但仍然标准的整数类型
<cstdint>
类型,如uint64_t
、int32_t
;这些类型保证了位宽和符号性,与架构无关。当您需要非常具体的整数宽度时,请使用这些类型。size_t
用于计数事物或内存偏移量。size_t
保证足够大,可以表示任何内存字节偏移量;即,它将与目标系统的本机指针类型一样宽。优先使用它而不是uint64_t
用于计数/偏移量,这样 32 位系统就不需要为 64 位值的额外开销付费。ssize_t
用于某些 ATen 兼容情况,其中Tensor
返回一个带符号的计数。如果可能,优先使用size_t
。
浮点运算¶
并非所有系统都支持浮点运算:有些系统甚至在其工具链中都没有启用浮点仿真。因此,核心运行时代码在运行时不能执行任何浮点运算,尽管创建或管理 float
或 double
值是可以的(例如,在 EValue
中)。
内核位于核心运行时之外,允许执行浮点运算。尽管某些内核可能选择不执行,以便它们可以在没有浮点支持的系统上运行。
日志记录¶
ExecuTorch 运行时在 //executorch/runtime/platform/log.h
中提供了 ET_LOG
接口,在 //executorch/runtime/platform/assert.h
中提供了 ET_CHECK
接口,而不是使用 printf()
、fprintf()
、cout
、cerr
或 folly::logging
或 glog
等库。消息使用 PAL 中的钩子打印,这意味着客户端可以将它们重定向到任何底层日志记录系统,或者如果可用,则只将它们打印到 stderr
。
日志格式可移植性¶
定宽整数¶
当您有一个像这样的日志语句时
int64_t value;
ET_LOG(Error, "Value %??? is bad", value);
对于 %???
部分,应该填入什么才能与 int64_t
匹配?在不同的系统上,int64_t
类型定义可能是 int
、long int
或 long long int
。选择像 %d
、%ld
或 %lld
这样的格式可能在一个目标上有效,但在其他目标上会出错。
为了实现可移植性,运行时代码使用来自 <cinttypes>
的标准(但不可否认有些笨拙)的辅助宏。每个可移植的整数类型都有一个对应的 PRIn##
宏,例如:
int32_t
->PRId32
uint32_t
->PRIu32
int64_t
->PRId64
uint64_t
->PRIu64
更多信息请参见 https://cppreference.cn/w/cpp/header/cinttypes
这些宏是文字字符串,可以与格式字符串的其他部分连接,例如:
int64_t value;
ET_LOG(Error, "Value %" PRId64 " is bad", value);
请注意,这需要将文字格式字符串分割(额外的双引号)。它还需要在宏之前加上 %
。
但是,通过使用这些宏,可以确保工具链将使用适合该类型的格式模式。
强制转换¶
有时,特别是在跨越 ATen 和精简模式的代码中,值的类型本身在不同构建模式下可能不同。在这些情况下,将值强制转换为精简模式类型,例如
ET_CHECK_MSG(
input.dim() == output.dim(),
"input.dim() %zd not equal to output.dim() %zd",
(ssize_t)input.dim(),
(ssize_t)output.dim());
在这种情况下,Tensor::dim()
在精简模式下返回 ssize_t
,而 at::Tensor::dim()
在 ATen 模式下返回 int64_t
。由于它们在概念上都返回(带符号的)计数,因此 ssize_t
是最合适的整数类型。 int64_t
可以工作,但它会不必要地要求 32 位系统在精简模式下处理 64 位值。
这是唯一需要强制转换的情况,即精简模式和 ATen 模式不一致。否则,使用与类型匹配的格式模式。