• 文档 >
  • 便携式 C++ 编程
快捷方式

便携式 C++ 编程

注意:本文档涵盖需要在目标硬件环境中构建和执行的代码。这适用于核心执行运行时,以及此仓库中的内核和后端实现。这些规则不一定适用于仅在开发主机上运行的代码,例如创作或构建工具。

ExecuTorch 运行时代码旨在具有可移植性,并且应该可以为各种系统构建,从服务器到手机再到 DSP,从 POSIX 到 Windows 再到裸机环境。

这意味着它不能假定存在

  • 文件

  • 线程

  • 异常

  • stdout, stderr

  • printf(), fprintf()

  • 一般的 POSIX API 和概念

它也不能假定

  • 64 位指针

  • 给定整数类型的大小

  • char 的有符号性

为了最大限度地减少二进制文件大小,并严格控制内存分配,代码可能不使用

  • malloc(), free()

  • new, delete

  • 大多数 stdlibc++ 类型;特别是管理自身内存的容器类型,如 stringvector,或内存管理包装器类型,如 unique_ptrshared_ptr

为了帮助降低复杂性,代码可能不依赖任何外部依赖项,除了

  • flatbuffers(用于 .pte 文件反序列化)

  • flatcc(用于事件跟踪序列化)

  • 核心 PyTorch(仅适用于 ATen 模式)

平台抽象层 (PAL)

为了避免假定目标系统的功能,ExecuTorch 运行时允许客户端覆盖其平台抽象层 (PAL) 中的底层函数,PAL 在 //executorch/runtime/platform/platform.h 中定义,以执行以下操作,例如

  • 获取当前时间戳

  • 打印日志消息

  • 使系统崩溃

内存分配

运行时代码应使用客户端提供的 MemoryManager (//executorch/runtime/executor/memory_manager.h) 分配内存,而不是使用 malloc()new

文件加载

客户端应提供已加载数据的缓冲区,或包装在 DataLoader 等类型中,而不是直接加载文件。

整数类型

ExecuTorch 运行时代码不应假定关于原始类型(如 intshortchar)大小的任何信息。例如,C++ 标准仅保证 int 至少为 16 位宽。并且 ARM 工具链将 char 视为无符号类型,而其他工具链通常将其视为有符号类型。

相反,运行时 API 使用一组更可预测但仍是标准的整数类型

  • <cstdint> 类型,如 uint64_tint32_t;这些类型保证了位宽和有符号性,而与架构无关。当您需要非常特定的整数宽度时,请使用这些类型。

  • size_t 用于事物计数或内存偏移量。size_t 保证足够大,可以表示任何内存字节偏移量;即,它将与目标系统的本机指针类型一样宽。对于计数/偏移量,首选使用此类型而不是 uint64_t,以便 32 位系统无需支付 64 位值的不必要开销。

  • ssize_t 用于某些 ATen 兼容性情况,其中 Tensor 返回有符号计数。尽可能首选 size_t

浮点运算

并非每个系统都支持浮点运算:有些系统甚至在其工具链中不启用浮点仿真。因此,核心运行时代码在运行时不得执行任何浮点运算,尽管简单地创建或管理 floatdouble 值(例如,在 EValue 中)是可以的。

内核位于核心运行时之外,允许执行浮点运算。尽管某些内核可以选择不执行浮点运算,以便它们可以在不支持浮点运算的系统上运行。

日志记录

ExecuTorch 运行时提供了 ET_LOG 接口(在 //executorch/runtime/platform/log.h 中)和 ET_CHECK 接口(在 //executorch/runtime/platform/assert.h 中),而不是使用 printf()fprintf()coutcerr 或像 folly::loggingglog 这样的库。消息使用 PAL 中的钩子打印,这意味着客户端可以将它们重定向到任何底层日志记录系统,或者如果可用,只需将它们打印到 stderr

日志格式可移植性

固定宽度整数

当您有如下日志语句时

int64_t value;
ET_LOG(Error, "Value %??? is bad", value);

对于 %??? 部分,您应该放什么来匹配 int64_t?在不同的系统上,int64_t 类型定义可能是 intlong intlong 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);

请注意,这需要分割文字格式字符串(额外的双引号)。它还需要宏之前的 % 前导符。

但是,通过使用这些宏,您可以保证工具链将为该类型使用适当的格式模式。

size_t, ssize_t

与固定宽度整数类型不同,格式字符串已经有一种可移植的方式来处理 size_tssize_t

  • size_t -> %zu

  • ssize_t -> %zd

类型转换

有时,尤其是在跨越 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 模式不一致时。否则,请使用与类型匹配的格式模式。

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

获取面向初学者和高级开发者的深度教程

查看教程

资源

查找开发资源并获得问题解答

查看资源