Yur*_*rik 4 c x86 gcc inline-assembly compiler-optimization
如果可用(在运行时测试),实现使用特定 CPU 指令的同一功能的多个版本的最佳方法是什么,或者如果没有,则回退到较慢的实现?
例如,x86 BMI2 提供了非常有用的PDEP指令。我将如何编写 C 代码,以便它在启动时测试正在执行的 CPU 的 BMI2 可用性,并使用两种实现之一——一种使用_pdep_u64调用(可用-mbmi2),另一种使用 C 进行“手动”位操作代码。是否有对此类情况的内置支持?在提供对较新的内在函数的访问的同时,如何让 GCC 为较旧的 arch 编译?我怀疑如果通过全局函数指针而不是每次都使用 if/else 调用函数,执行速度会更快吗?
您可以声明一个函数指针,并在程序启动时通过调用cpuid确定当前架构将其指向正确的版本
但是最好利用许多现代编译器的支持。Intel的ICC早就有自动功能调度,可以为每个架构选择优化的版本。我不知道细节,但看起来它只适用于英特尔的库。此外它只发送到 Intel CPU 上的高效版本,因此对其他制造商不公平。Agner 的 CPU 博客中有许多补丁和解决方法
后来在GCC 4.8中引入了一个名为Function Multiversioning的特性。它添加了您将在函数的每个版本上声明的属性target
__attribute__ ((target ("sse4.2")))
int foo() { return 1; }
__attribute__ ((target ("arch=atom")))
int foo() { return 2; }
int main() {
int (*p)() = &foo;
return foo() + p();
}
Run Code Online (Sandbox Code Playgroud)
这会复制大量代码并且很麻烦,因此 GCC 6 添加了target_clones它告诉 GCC 将一个函数编译为多个克隆。例如__attribute__((target_clones("avx2","arch=atom","default"))) void foo() {}将创建 3 个不同的foo版本。关于它们的更多信息可以在GCC 的关于函数属性的文档中找到
该语法随后被Clang和ICC采纳。性能甚至可以比全局函数指针更好,因为函数符号可以在进程加载时而不是运行时解析。这是英特尔的 Clear Linux运行速度如此之快的原因之一。ICC 还可以在自动矢量化期间创建单个循环的多个版本
这是多版本控制(第二部分)中的一个例子,以及它的关于 popcnt 的演示,但你明白了
__attribute__((target_clones("popcnt","default")))
int runPopcount64_builtin_multiarch_loop(const uint8_t* bitfield, int64_t size, int repeat) {
int res = 0;
const uint64_t* data = (const uint64_t*)bitfield;
for (int r=0; r<repeat; r++)
for (int i=0; i<size/8; i++) {
res += popcount64_builtin_multiarch_loop(data[i]);
}
return res;
}
Run Code Online (Sandbox Code Playgroud)
请注意,PDEP并PEXT是目前AMD CPU的速度很慢,使他们只能在英特尔启用
| 归档时间: |
|
| 查看次数: |
272 次 |
| 最近记录: |