原文地址:ARM Cortex-A Processors and GCC Command Lines
原作者:richardearnshaw
面向 ARM 处理器的 GNU 编译器集合(GCC) 命令行选项最初是在多年前设计的,当时可用处理器和变体产品的列表与现今相比要短很多。 随着 ARM 架构的演进,从 GCC 中获取最佳代码所需的选项也已改变,但已做了各种尝试以确保现有选项集不会改变其本意。设计编译器意味着最有效使用 ARM CortexTM-A 处理器所需的选项目前相当复杂。本博文包含 GCC 命令行选项的三个方面:CPU、浮点和 SIMD(单指令多数据)加速。
首先,让我们了解下用于告知编译器您正在使用的 CPU 的主要选项;稍后,我们将讨论一些在特殊情况下可使用的更高级选项。
每当您编译文件时,编译器都需要知道您试图用于运行目标代码的 CPU 的类型。实现这一目的的首选为 -mcpu=<cpu-name> 选项。正如您预想的那样,cpu-name 已替换为您拥有的 CPU 类型的具体名称,但使用小写。例如,对于 Cortex-A9 而言,选项为 -mcpu=cortex-a9。GCC 目前支持 Cortex-A15 (包括其本身在内)之前的所有 Cortex-A 处理器;即:
Cortex-A5 -mcpu=cortex-a5
Cortex-A7 -mcpu=cortex-a7
Cortex-A8 -mcpu=cortex-a8
Cortex-A9 -mcpu=cortex-a9
Cortex-A15 -mcpu=cortex-a15
如果您的 GCC 版本不识别上述任一处理器,则可能是其版本过低,您应当考虑进行升级。如果您不指定要使用的 CPU,GCC 将使用内置的默认值 - 该默认值根据编译器最初的构建方式而变化,并且可能意味着已生成代码在您所拥有的 CPU 上的执行速度将非常缓慢(或根本无法执行)。
目前,市面上的所有 ARM Cortex-A 处理器均配备浮点运算单元,大多数还拥有可实施 ARM 高级 SIMD 处理器扩展的 SIMD 单元(通常称为 NEONTM)。但是,可用的精确指令集取决于拥有的处理器,而且 GCC 需要单独的选项来进行控制;它不会尝试通过 -mcpu 选项进行处理。浮点和 SIMD 指令的选择由 -mfpu 选项控制,下表列出了各种 CPU 的建议选择项:
VFPv3 和 VFPv4 实施最初通过 32 个双精度寄存器开始,但是在没有 NEON 时,则可以任选前 16 个寄存器;这由选项名的 d16 部分所控制的。该名称的 fp16 部分指定是否存在半精度(16 位)浮点负载、存储和转换指令;这是对 VFPv3 的扩展,但适用于所有 VFPv4 实施。
出于历史原因,GCC 只在被明确告知可以安全使用浮点和 NEON 指令时才会使用这些指令。在此进行控制的选项有些令人混淆,选项的一部分也可更改编译器所遵守的 ABI。-mfloat-abi 选项有三种可能的选项:
-mfloat-abi=soft -- 忽略所有 FPU 和 NEON 指令,仅使用核心寄存器集并利用库调用模拟所有浮点运算。
-mfloat-abi=softfp -- 使用与 -float-abi=soft 相同的调用规则,但会在适用情况下使用浮点和 NEON 指令。此选项为二进制,与 -mfloat-abi=soft 兼容,并且可以用于提高必须遵循软浮点环境的代码的性能,但需要是已知相关硬件指令也可用的环境。
-mfloat-abi=hard -- 在适用情况下使用浮点和 NEON 指令,并且还更改ABI 调用规则以便生成更高效的函数调用;现在可以在扩展寄存器中的函数之间传递浮点和矢量类型,不仅节约大量复制操作,而且也意味着需要在堆栈上传递参数的调用变少。
应当使用上述哪个选项在很大程度上取决于您的目标系统,也可能默认选项就是正确的选项。以Ubuntu 12.04 (Precise) 为例,其目前在默认情况下使用的是 -mfloat-abi=hard。
NEON 架构包含同时对整数和浮点数据类型进行运算的指令,并且 GCC 目前拥有功能强大的自动矢量化优化,可发现何时适合使用可改善性能的矢量引擎。不过,让许多用户惊讶的是,即使他们可能期望这么做,编译器也无法将其代码矢量化。
首先要记住的是,自动矢量化程序只有在 -O3 时才默认启用。存在可以在其他时候将它打开的选项;您可以在 GCC 手册中找到相关内容。
然而,即使启用了矢量化程序,浮点代码经常也无法实现矢量化。原因在于,尽管 NEON 中的浮点运算使用 IEEE 单精度格式来容纳值,但为了最大限度降低 NEON 单元中所需的功率并且最大限度提高吞吐量,只有在输入和结果在正常运算范围内(即值不是非正常值或 NaN)时,矢量引擎才会完全按照标准进行编译。GCC 默认配置为生成严格遵守 IEEE 浮点算法规范的代码,前文所述的限制意味着默认使用 SIMD 指令是不适当的。
幸运的是,GCC 确实提供了多个命令行选项,可用于准确控制需要的 IEEE 标准遵守级别。尽管具体细节不在此讨论,但在大多数情形中,使用 -ffast-math 选项来放宽规则并启用矢量化是绝对安全的。
您也可以在 GCC 4.6 或更高版本中使用 -Ofast 选项来实现基本相同的效果。它会打开 -O3 以及多种其他优化,通常可以安全地用于从您的代码中获得最佳的性能。
要记住的另一点是,NEON 仅支持单精度数据的矢量运算。除非写入的代码用于处理这一格式,否则可能会发现矢量化并不可行。您也应当了解,浮点常量(字面值)最终会强制编译器以双精度执行计算。 在 C 和 C++ 环境下,编写 '1.0F' 而非 '1.0' 以确保编译器知道您的意思。
最后,如果对于找出矢量化程序为何不按您预期运作的原因依然存有疑问,而且您也准备好亲自动手探究,GCC 对于其所作的行为提供了非常丰富的信息。-fdump-tree-vect 和 -ftree-vectorizer-verbose=<level> 选项控制所生成的信息量,级别是 1 到 9 范围内的数字。尽管生成的大多数信息只有编译器开发人员才会感兴趣,但您不时也会在输出结果中找到提示,了解您的代码不能按预期矢量化的原因。
既然拥有大量选项,那我在日常运算中应使用哪些选项呢?幸运的是,目标环境一旦确定后,大多数选项就不会定期发生变化了。以下是一些示例:
含 NEON 的 Cortex-A15 处理器,以及操作使用“浮点”数据类型的数据阵列的一些浮点代码。该运算环境可以支持在浮点寄存器中传递参数:
arm-gcc -O3 -mcpu=cortex-a15 -mfpu=neon-vfpv4 -mfloat-abi=hard \
-ffast-math -o myprog.exe myprog.c
不含 NEON 的 Cortex-A7 处理器,处理浮点代码。该运算环境仅支持在整数寄存器中传递参数,但可以使用浮点硬件
arm-gcc -O3 -mcpu=cortex-a7 -mfpu=vfpv4-d16 -mfloat-abi=softfp \
-o myprog2.exe myprog2.c
最后,Cortex-A9 处理器,用于在浮点/NEON 寄存器集全都无法使用的环境中进行运算(例如,因为它在中断处理程序的中间,并且浮点上下文保留用于用户状态)。
arm-gcc -O3 -mcpu=cortex-a9 -mfloat-abi=soft -c -o myfile.o myfile.c
richardearnshaw 是 ARM 首席工程师,也是 ARM GNU 编译器工具团队的技术总管和软件架构师。除了其他工作外,他在编译器工作方面拥有将近 20 年的经验,也是 GCC 全球评论员的一员。
這篇很實用