使用较新的 CPU 指令支持构建向后兼容的二进制文件
Building backward compatible binaries with newer CPU instructions support
如果可用(在 运行 时间测试)使用特定 CPU 指令实现同一功能的多个版本的最佳方法是什么,如果不可用则回退到较慢的实现?
例如,x86 BMI2 提供了非常有用的PDEP指令。我将如何编写 C 代码,以便它在启动时测试执行 CPU 的 BMI2 可用性,并使用两种实现之一——一种使用 _pdep_u64
调用(可用于 -mbmi2
),另一个使用 C 代码进行位操作 "by hand"。是否有针对此类情况的内置支持?我如何让 GCC 为旧的 arch 编译,同时提供对较新的内部函数的访问?我怀疑如果通过全局函数指针调用函数执行速度会更快,而不是每次都调用 if/else?
您可以通过调用cpuid
来声明一个函数指针并在程序启动时将其指向正确的版本,以确定当前的体系结构
但最好利用许多现代编译器的支持。英特尔的 ICC 有 automatic function dispatching to select the optimized version for each architecture long ago. I don't know the details but looks like it only applies to Intel's libraries. Besides it only dispatches to the efficient version on Intel CPUs, hence would be unfair to other manufacturers. There are many patches and workarounds for that in Agner`s CPU blog
稍后称为 Function Multiversioning was introduced in GCC 4.8 的功能。它添加了您将在函数的每个版本上声明的 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();
}
这会重复很多代码而且很麻烦,因此 GCC 6 添加了 target_clones
告诉 GCC 将函数编译为多个克隆。例如 __attribute__((target_clones("avx2","arch=atom","default"))) void foo() {}
将创建 3 个不同的 foo
版本。有关它们的更多信息,请参阅 GCC's documentation about function attribute
Clang and ICC. Performance can even be better than a global function pointer because the function symbols can be resolved at process loading time instead of runtime. It's one of the reasons Intel's Clear Linux runs so fast. ICC may also create multiple versions of a single loop 在自动矢量化过程中采用了语法
- Function multi-versioning in GCC 6
- Function Multi-Versioning
- The - surprisingly limited - usefulness of function multiversioning in GCC
这里有一个来自 The one with multi-versioning (Part II) along with its demo 的例子,它是关于 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;
}
请注意 因此它们应该只在 Intel 上启用
如果可用(在 运行 时间测试)使用特定 CPU 指令实现同一功能的多个版本的最佳方法是什么,如果不可用则回退到较慢的实现?
例如,x86 BMI2 提供了非常有用的PDEP指令。我将如何编写 C 代码,以便它在启动时测试执行 CPU 的 BMI2 可用性,并使用两种实现之一——一种使用 _pdep_u64
调用(可用于 -mbmi2
),另一个使用 C 代码进行位操作 "by hand"。是否有针对此类情况的内置支持?我如何让 GCC 为旧的 arch 编译,同时提供对较新的内部函数的访问?我怀疑如果通过全局函数指针调用函数执行速度会更快,而不是每次都调用 if/else?
您可以通过调用cpuid
来声明一个函数指针并在程序启动时将其指向正确的版本,以确定当前的体系结构
但最好利用许多现代编译器的支持。英特尔的 ICC 有 automatic function dispatching to select the optimized version for each architecture long ago. I don't know the details but looks like it only applies to Intel's libraries. Besides it only dispatches to the efficient version on Intel CPUs, hence would be unfair to other manufacturers. There are many patches and workarounds for that in Agner`s CPU blog
稍后称为 Function Multiversioning was introduced in GCC 4.8 的功能。它添加了您将在函数的每个版本上声明的 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();
}
这会重复很多代码而且很麻烦,因此 GCC 6 添加了 target_clones
告诉 GCC 将函数编译为多个克隆。例如 __attribute__((target_clones("avx2","arch=atom","default"))) void foo() {}
将创建 3 个不同的 foo
版本。有关它们的更多信息,请参阅 GCC's documentation about function attribute
Clang and ICC. Performance can even be better than a global function pointer because the function symbols can be resolved at process loading time instead of runtime. It's one of the reasons Intel's Clear Linux runs so fast. ICC may also create multiple versions of a single loop 在自动矢量化过程中采用了语法
- Function multi-versioning in GCC 6
- Function Multi-Versioning
- The - surprisingly limited - usefulness of function multiversioning in GCC
这里有一个来自 The one with multi-versioning (Part II) along with its demo 的例子,它是关于 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;
}
请注意