可移植 C++ 编程¶
注意:本文档涵盖了构建和在目标硬件环境中执行所需的代码。这适用于核心执行运行时,以及此存储库中的内核和后端实现。这些规则不一定适用于仅在开发主机上运行的代码,例如创作或构建工具。
ExecuTorch 运行时代码旨在可移植,并且应该能够为各种系统构建,从服务器到手机到 DSP,从 POSIX 到 Windows 到裸机环境。
这意味着它不能假定存在
文件
线程
Exceptions
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 中定义,以执行诸如以下操作:
获取当前时间戳
打印日志消息
系统崩溃
内存分配¶
运行时代码应使用客户端提供的 MemoryManager(//executorch/runtime/executor/memory_manager.h)来分配内存,而不是使用 malloc() 或 new。
文件加载¶
客户端应提供已加载数据的缓冲区,或将其包装在 DataLoader 等类型中,而不是直接加载文件。
整数类型¶
ExecuTorch 运行时代码不应假定 int、short 或 char 等原始类型的大小。例如,C++ 标准仅保证 int 至少有 16 位宽。ARM 工具链将 char 视为无符号,而其他工具链通常将其视为有符号。
相反,运行时 API 使用一组更可预测但仍然标准的整数类型:
<cstdint>类型,如uint64_t、int32_t;这些类型保证了位宽和有符号性,与架构无关。当您需要非常特定的整数宽度时,请使用这些类型。size_t用于计数或内存偏移量。size_t保证足够大,可以表示任何内存字节偏移量;也就是说,它将与目标系统的本机指针类型一样宽。为了避免 32 位系统不必要地处理 64 位值的开销,在计数/偏移量方面,优先使用此类型而不是uint64_t。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->PRId32uint32_t->PRIu32int64_t->PRId64uint64_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,而在 ATen 模式下 at::Tensor::dim() 返回 int64_t。由于它们都概念上返回(有符号)计数,因此 ssize_t 是最合适的整数类型。int64_t 也可以工作,但它会在精简模式下不必要地要求 32 位系统处理 64 位值。
这是唯一需要进行类型转换的情况,即精简模式和 ATen 模式不一致时。否则,使用与类型匹配的格式模式。