可移植的 C++ 编程¶
注意:本文档涵盖需要在目标硬件环境中构建并执行的代码。这适用于本仓库中的核心执行运行时,以及内核和后端实现。这些规则不一定适用于仅在开发主机上运行的代码,例如编写或构建工具。
ExecuTorch 运行时代码旨在具备可移植性,应能在多种系统上构建,涵盖从服务器到移动设备再到 DSP,从 POSIX 到 Windows 再到裸机环境。
这意味着它无法假定以下内容的存在:
文件
线程
异常
stdout,stderrprintf(),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中)是可以的。
内核位于核心运行时之外,因此允许执行浮点运算。不过,某些内核可能会选择不执行此类运算,以便在没有浮点支持的系统上运行。
日志记录¶
不使用 printf()、fprintf()、cout、cerr 或像 folly::logging 或 glog 这样的库,ExecuTorch 运行时在 //executorch/runtime/platform/log.h 中提供 ET_LOG 接口,并在 //executorch/runtime/platform/assert.h 中提供 ET_CHECK 接口。消息通过 PAL 中的钩子进行打印,这意味着客户端可以将它们重定向到任何底层日志系统,或者如果可用,直接打印到 stderr。
日志格式可移植性¶
固定宽度整数¶
当您有一条日志语句,例如
int64_t value;
ET_LOG(Error, "Value %??? is bad", value);
对于 %??? 部分,您应该填入什么内容以匹配 int64_t?在不同的系统上,int64_t typedef 可能是 int、long int 或 long long int。
选择像 %d、%ld 或 %lld 这样的格式可能在某个目标平台上有效,但在其他平台上会失效。
为了具备可移植性,运行时代码使用了标准(尽管 admittedly 笨拙)的辅助宏,这些宏来自 <cinttypes>。每种可移植整数类型都有一个对应的 PRIn## 宏,例如
int32_t->PRId32uint32_t->PRIu32int64_t->PRId64uint64_t->PRIu64有关更多信息,请参阅 https://en.cppreference.com/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() 在 lean 模式下返回 ssize_t,而
at::Tensor::dim() 在 ATen 模式下返回 int64_t。由于它们在概念上都返回(有符号)计数,因此 ssize_t 是最合适的整数类型。
int64_t 也可以工作,但这会不必要地要求 32 位系统在 lean 模式下处理 64 位值。
这是唯一需要强制转换的情况,即 lean 模式与 ATen 模式不一致时。否则,请使用与类型匹配的格式模式。