根据支持的指令选择要使用的程序集实现
Choose assembly implementation to use based on supported instructions
我正在开发一个 C 库,它 compiles/links 到一个 .a
文件,用户可以静态地 link 到他们的代码中。该库的性能非常重要,因此我正在 x86-64 汇编中编写性能关键例程以优化性能。
对于某些例程,如果我使用 BMI2 指令,我可以获得比我坚持使用 "standard" x86-64 指令集更好的性能。问题是,BMI2 是最近才推出的,我的一些用户使用的处理器不支持这些指令。
因此,我编写了优化例程 两次 ,一次使用 BMI2 指令,一次不使用它们。在我当前的设置中,我将分发两个版本的 .a
文件:一个 "fast" 一个需要支持 BMI2 指令,另一个 "slow" 不需要支持 BMI2 指令。
我问是否有一种方法可以通过分发单个 .a
文件来简化此过程,该文件将根据 CPU 应用程序 运行s 支持 BMI2 指令。
与 Whosebug 上的类似问题不同,这里有两个特点:
- 选择功能的技术需要在关键路径中具有特别低的开销。有问题的例程,在汇编优化后,运行 在 ~10 ns 内,因此即使是单个
if
语句也可能很重要。
- 需要选择的功能"dynamically"在开始时选择一次,然后在程序运行期间保持不变。我希望这将提供比这个问题中建议的解决方案更快的解决方案:Choosing method implementation at runtime
到目前为止,我想出的最快的解决方案是执行以下操作:
- 检查CPU是否支持使用
cpuid
指令的BMI2指令
- 根据结果设置全局变量
true
或false
。
- 在每次函数调用时根据此全局变量的值进行分支。
我对这种方法不满意,因为它有两个缺点:
- 我不确定如何 自动 运行
cpuid
并在程序开始时设置一个全局变量,假设我'我正在分发 .a
文件并且无法控制最终二进制文件中的 main
函数。 如果 C++ 提供了更好的解决方案,我很乐意在这里使用它,只要最终的库仍然可以 link 编辑并从 C 程序调用。
- 这会在 每次 函数调用时产生开销,而理想情况下唯一的开销是在程序启动时。
有没有比我上面详述的解决方案更有效的解决方案?
x264 使用一个 init 函数(库的用户需要在调用其他任何东西之前调用该函数,或类似的东西)根据 CPUID 结果设置一个函数指针结构。包括考虑到 pshufb
在一些支持它的早期 CPU 上很慢。
如果你的函数依赖于 pdep
/ pext
,你可能想要检测 AMD 与 Intel,因为 AMD 的 pdep
/pext
非常慢并且可能不值得在 Ryzen 上使用,即使它可用。 (有关说明表,请参阅 https://agner.org/optimize/。)
函数指针的开销相当低,与调用共享库或 DLL 中的函数大致相同。 call [rel funcptr]
而不是 call func
。 (在调用您的函数的编译器生成的 asm 中)。
CPU dependent code: how to avoid function pointers? 在 C 中显示了一个非常简单的示例,并寻求 避免 它的方法。使用动态 linking,您可以在动态 link 时间进行 CPU 检测,因此动态-linking 间接也成为您的 CPU-dispatch 间接(就像 glibc 选择优化的 memcpy
实现一样。)
但是对于 .a
的静态 linking,只需创建静态初始化为基线版本的函数指针,以及您的 CPU 初始化函数(希望在任何之前运行的函数指针被取消引用)重写它们以指向当前 CPU.
的最佳版本
如果您使用的是 gcc,您可以让编译器自动执行所有样板代码。 gcc manual page on function multiversioning
我正在开发一个 C 库,它 compiles/links 到一个 .a
文件,用户可以静态地 link 到他们的代码中。该库的性能非常重要,因此我正在 x86-64 汇编中编写性能关键例程以优化性能。
对于某些例程,如果我使用 BMI2 指令,我可以获得比我坚持使用 "standard" x86-64 指令集更好的性能。问题是,BMI2 是最近才推出的,我的一些用户使用的处理器不支持这些指令。
因此,我编写了优化例程 两次 ,一次使用 BMI2 指令,一次不使用它们。在我当前的设置中,我将分发两个版本的 .a
文件:一个 "fast" 一个需要支持 BMI2 指令,另一个 "slow" 不需要支持 BMI2 指令。
我问是否有一种方法可以通过分发单个 .a
文件来简化此过程,该文件将根据 CPU 应用程序 运行s 支持 BMI2 指令。
与 Whosebug 上的类似问题不同,这里有两个特点:
- 选择功能的技术需要在关键路径中具有特别低的开销。有问题的例程,在汇编优化后,运行 在 ~10 ns 内,因此即使是单个
if
语句也可能很重要。 - 需要选择的功能"dynamically"在开始时选择一次,然后在程序运行期间保持不变。我希望这将提供比这个问题中建议的解决方案更快的解决方案:Choosing method implementation at runtime
到目前为止,我想出的最快的解决方案是执行以下操作:
- 检查CPU是否支持使用
cpuid
指令的BMI2指令 - 根据结果设置全局变量
true
或false
。 - 在每次函数调用时根据此全局变量的值进行分支。
我对这种方法不满意,因为它有两个缺点:
- 我不确定如何 自动 运行
cpuid
并在程序开始时设置一个全局变量,假设我'我正在分发.a
文件并且无法控制最终二进制文件中的main
函数。 如果 C++ 提供了更好的解决方案,我很乐意在这里使用它,只要最终的库仍然可以 link 编辑并从 C 程序调用。 - 这会在 每次 函数调用时产生开销,而理想情况下唯一的开销是在程序启动时。
有没有比我上面详述的解决方案更有效的解决方案?
x264 使用一个 init 函数(库的用户需要在调用其他任何东西之前调用该函数,或类似的东西)根据 CPUID 结果设置一个函数指针结构。包括考虑到 pshufb
在一些支持它的早期 CPU 上很慢。
如果你的函数依赖于 pdep
/ pext
,你可能想要检测 AMD 与 Intel,因为 AMD 的 pdep
/pext
非常慢并且可能不值得在 Ryzen 上使用,即使它可用。 (有关说明表,请参阅 https://agner.org/optimize/。)
函数指针的开销相当低,与调用共享库或 DLL 中的函数大致相同。 call [rel funcptr]
而不是 call func
。 (在调用您的函数的编译器生成的 asm 中)。
CPU dependent code: how to avoid function pointers? 在 C 中显示了一个非常简单的示例,并寻求 避免 它的方法。使用动态 linking,您可以在动态 link 时间进行 CPU 检测,因此动态-linking 间接也成为您的 CPU-dispatch 间接(就像 glibc 选择优化的 memcpy
实现一样。)
但是对于 .a
的静态 linking,只需创建静态初始化为基线版本的函数指针,以及您的 CPU 初始化函数(希望在任何之前运行的函数指针被取消引用)重写它们以指向当前 CPU.
如果您使用的是 gcc,您可以让编译器自动执行所有样板代码。 gcc manual page on function multiversioning