使用 Clang 进行交叉编译时,如何实现原生级别的优化?
How can I achieve native-level optimizations when cross-compiling with Clang?
当使用 clang
和 -target
选项进行交叉编译时,针对与本机系统相同的体系结构和硬件,我注意到 clang
似乎产生了更差的优化对于三元组中 <sys>
为 none
.
的情况,比本地构建的对应部分
考虑这个简单的代码示例:
int square(int num) {
return num * num;
}
当使用 -target x86_64-linux-elf
在 -O3
优化时,本机 x86_64
目标代码生成产生:
square(int):
mov eax, edi
imul eax, edi
ret
用 -target x86_64-none-elf
生成的代码产生:
square(int):
push rbp
mov rbp, rsp
mov eax, edi
imul eax, edi
pop rbp
ret
尽管具有相同的硬件和优化标志,但显然缺少优化。如果在目标三元组中将 none
替换为 linux
,问题就会消失,尽管没有使用系统特定的功能。
乍一看它似乎根本没有优化,但不同的代码段显示它正在执行一些 ] 优化,但不是全部。例如,loop-unrolling is still occurring.
虽然上面的例子只是 x86_64,但在实践中,这个问题正在为基于 armv7
的受限嵌入式系统生成代码膨胀,我注意到在某些情况,例如:
- 不删除不必要的 setup/cleanup 指令(与 x86_64 相同)
- 当内联
vector
-like push_back
调用时,不将某些顺序内联增量合并为单个 add
指令(在 -Os
处)。这在从本地构建时进行了优化基于 arm 的系统 运行 armv7.)
- 不将相邻的小整数值合并为单个
mov
(例如在 optional
实现中将 32 位 int
与 bool
合并。这优化了当从基于 arm 的系统本地构建时 运行 armv7.)
- 等等
我想知道我能做些什么,如果有的话,在交叉编译时实现与本机编译相同的优化?是否有任何相关标志可以帮助调整三元组中 <sys>
以某种方式暗示的调整?
如果可能的话,我很想知道 为什么 交叉编译目标似乎无法优化某些事情,仅仅是因为系统不同,尽管具有相同的系统架构和能力。我的理解是 LLVM 的优化器在 IR 上运行,只要不依赖于目标系统本身,它应该有效地生成相同的优化。
TL;DR: 对于 x86 目标,当 OS 未知时,默认启用 frame-pointers。您可以使用 -fomit-frame-pointer
手动禁用它们。对于ARM平台,你当然需要提供更多的信息,以便后端推导出目标ABI。使用 -emit-llvm
so 检查 Clang/LLVM 的哪一部分生成了低效代码。
应用程序二进制接口 (ABI) 可以从一个目标更改为另一个目标。 C 中没有标准 ABI。选择一个取决于几个参数,包括体系结构、版本、供应商、OS、环境等
-target
参数的使用帮助编译器select ABI。问题是 x86_64-none-elf
不够完整,因此后端实际上可以生成快速代码。事实上,我认为这实际上不是一个有效的目标,因为在这种情况下有来自 Clang 的警告,并且相同的警告出现在错误的随机目标上。令人惊讶的是,编译器仍然可以使用提供的信息成功生成 通用二进制文件 。 x86_64-windows
和 x86_64-linux
等目标与 x86_64-unknown-windows-cygnus
一样有效(对于 Windows 中的 Cygwin)。您可以获得 Clang 支持的平台、OS、环境等的列表。in the code.
ABI 的一个特殊方面是调用约定。它们在操作系统之间是不同的。例如,x86-64 Linux 平台使用来自 System V AMD64 ABI 的调用约定,而最近的 x86-64 Windows 平台使用 vectorcall 调用约定基于旧的 Microsoft x64 之一。对于旧的 x86 架构,情况更为复杂。有关此的更多信息,请阅读 this and this。
在您的情况下,如果没有关于 OS 的信息,编译器可能 select 它自己的通用 ABI 导致使用旧的 push/pop 指令。也就是说,编译器假定 edi
包含传递给函数的参数,这意味着所选 ABI 是 System V AMD64(或派生版本)。环境可以在堆栈优化中发挥重要作用,例如从函数外部(例如被调用函数)访问堆栈。
我最初的猜测是,由于缺少有关指定目标的信息,程序集后端禁用了一些优化,但 x86-64 ELF 的情况并非如此,因为相同的后端是 selected。请注意,目标由 architecture-specific 后端解析(例如,请参见 here)。
事实上,问题来自 Clang 发出的 IR 代码,其中 frame-pointer 标志设置为 all
。您可以使用标志 -emit-llvm
检查 IR 代码。您可以使用 -fomit-frame-pointer
.
解决此问题
在 ARM 上,问题可能不同并且来自汇编后端。您当然应该指定目标 OS 或至少更多信息,例如 sub-architecture 类型(主要针对 ARM)、供应商和环境。
总体而言,请注意,由于缺乏信息,更通用的目标会产生效率较低的代码,这是合理的。如果您需要有关此的更多信息,请在 Clang 或 LLVM 错误跟踪器上填写问题,以便跟踪发生这种情况的原因 or/and 让开发人员修复此问题。
相关posts/links:
- clang: how to list supported target architectures?
- https://clang.llvm.org/docs/CrossCompilation.html
当使用 clang
和 -target
选项进行交叉编译时,针对与本机系统相同的体系结构和硬件,我注意到 clang
似乎产生了更差的优化对于三元组中 <sys>
为 none
.
考虑这个简单的代码示例:
int square(int num) {
return num * num;
}
当使用 -target x86_64-linux-elf
在 -O3
优化时,本机 x86_64
目标代码生成产生:
square(int):
mov eax, edi
imul eax, edi
ret
用 -target x86_64-none-elf
生成的代码产生:
square(int):
push rbp
mov rbp, rsp
mov eax, edi
imul eax, edi
pop rbp
ret
尽管具有相同的硬件和优化标志,但显然缺少优化。如果在目标三元组中将 none
替换为 linux
,问题就会消失,尽管没有使用系统特定的功能。
乍一看它似乎根本没有优化,但不同的代码段显示它正在执行一些 ] 优化,但不是全部。例如,loop-unrolling is still occurring.
虽然上面的例子只是 x86_64,但在实践中,这个问题正在为基于 armv7
的受限嵌入式系统生成代码膨胀,我注意到在某些情况,例如:
- 不删除不必要的 setup/cleanup 指令(与 x86_64 相同)
- 当内联
vector
-likepush_back
调用时,不将某些顺序内联增量合并为单个add
指令(在-Os
处)。这在从本地构建时进行了优化基于 arm 的系统 运行 armv7.) - 不将相邻的小整数值合并为单个
mov
(例如在optional
实现中将 32 位int
与bool
合并。这优化了当从基于 arm 的系统本地构建时 运行 armv7.) - 等等
我想知道我能做些什么,如果有的话,在交叉编译时实现与本机编译相同的优化?是否有任何相关标志可以帮助调整三元组中 <sys>
以某种方式暗示的调整?
如果可能的话,我很想知道 为什么 交叉编译目标似乎无法优化某些事情,仅仅是因为系统不同,尽管具有相同的系统架构和能力。我的理解是 LLVM 的优化器在 IR 上运行,只要不依赖于目标系统本身,它应该有效地生成相同的优化。
TL;DR: 对于 x86 目标,当 OS 未知时,默认启用 frame-pointers。您可以使用 -fomit-frame-pointer
手动禁用它们。对于ARM平台,你当然需要提供更多的信息,以便后端推导出目标ABI。使用 -emit-llvm
so 检查 Clang/LLVM 的哪一部分生成了低效代码。
应用程序二进制接口 (ABI) 可以从一个目标更改为另一个目标。 C 中没有标准 ABI。选择一个取决于几个参数,包括体系结构、版本、供应商、OS、环境等
-target
参数的使用帮助编译器select ABI。问题是 x86_64-none-elf
不够完整,因此后端实际上可以生成快速代码。事实上,我认为这实际上不是一个有效的目标,因为在这种情况下有来自 Clang 的警告,并且相同的警告出现在错误的随机目标上。令人惊讶的是,编译器仍然可以使用提供的信息成功生成 通用二进制文件 。 x86_64-windows
和 x86_64-linux
等目标与 x86_64-unknown-windows-cygnus
一样有效(对于 Windows 中的 Cygwin)。您可以获得 Clang 支持的平台、OS、环境等的列表。in the code.
ABI 的一个特殊方面是调用约定。它们在操作系统之间是不同的。例如,x86-64 Linux 平台使用来自 System V AMD64 ABI 的调用约定,而最近的 x86-64 Windows 平台使用 vectorcall 调用约定基于旧的 Microsoft x64 之一。对于旧的 x86 架构,情况更为复杂。有关此的更多信息,请阅读 this and this。
在您的情况下,如果没有关于 OS 的信息,编译器可能 select 它自己的通用 ABI 导致使用旧的 push/pop 指令。也就是说,编译器假定 edi
包含传递给函数的参数,这意味着所选 ABI 是 System V AMD64(或派生版本)。环境可以在堆栈优化中发挥重要作用,例如从函数外部(例如被调用函数)访问堆栈。
我最初的猜测是,由于缺少有关指定目标的信息,程序集后端禁用了一些优化,但 x86-64 ELF 的情况并非如此,因为相同的后端是 selected。请注意,目标由 architecture-specific 后端解析(例如,请参见 here)。
事实上,问题来自 Clang 发出的 IR 代码,其中 frame-pointer 标志设置为 all
。您可以使用标志 -emit-llvm
检查 IR 代码。您可以使用 -fomit-frame-pointer
.
在 ARM 上,问题可能不同并且来自汇编后端。您当然应该指定目标 OS 或至少更多信息,例如 sub-architecture 类型(主要针对 ARM)、供应商和环境。
总体而言,请注意,由于缺乏信息,更通用的目标会产生效率较低的代码,这是合理的。如果您需要有关此的更多信息,请在 Clang 或 LLVM 错误跟踪器上填写问题,以便跟踪发生这种情况的原因 or/and 让开发人员修复此问题。
相关posts/links:
- clang: how to list supported target architectures?
- https://clang.llvm.org/docs/CrossCompilation.html