我想将一个运行时CPU调度集成到我的库中。 我有几个版本的一些function,为sse2 / sse3 / avx和x87通用变体进行了优化。 我想编译所有版本到单个.so库,我想如何实现一个CPU调度。
我认为最快的方法是在链接步骤(dynamic链接)获取CPU调度,所以当ld.so将加载我的库,我想要检查,CPU支持sse2,sse3或avx,然后我想ld所以select正确的function。
例如(使用gcc目标属性 ):
图书馆:
float* func3_generic(float *a, float *b) __attribute__ ((__target__ ("fpmath=387"))); float* func3_sse2(float *a, float *b) __attribute__ ((__target__ ("sse2"))); float* func3_sse3(float *a, float *b) __attribute__ ((__target__ ("sse3"))); float* func3_avx(float *a, float *b) __attribute__ ((__target__ ("avx")));
我想有一些特殊的符号func3()
,它将由链接器(ld.so)设置为最先进的func3_generic
, func3_sse2
, func3_sse3
, func3_avx
。 所以,如果CPU是Core i7-xxxx,我希望每次调用func3将调用func3_avx,如果cpu是PentiumPro,调用func3将调用func3_generic。
同时,我不想手动编写大量的调度代码,我希望以最小的开销(没有额外的间接跳转)来select正确的variables。 这意味着我可以在应用程序启动时花费额外的时间,但是在调用这个函数时没有任何额外的东西(在某些情况下有很多的调用)。
UPDATE。 链接器可以根据AUXVvector进行调度, AT_HWCAP:
field:
$ LD_SHOW_AUXV=1 /bin/echo ... AT_HWCAP: fpu ... mmx fxsr sse sse2
使用dlopen
简单加载其中一个.so
是否可以接受? 你可以用各种方法查询CPU类型,然后选择合适的库来绑定func3
如何处理函数指针数组,然后使用CPUID指令使其在启动时指向特定的实现。 在启动时花费的周期数应该是微乎其微的。
如果你真的想要避免任何启动成本的另一种方法是编写另一个小程序,查询你的CPU的能力,然后用一些宏定义一个gcc的命令行,以便只编译某些函数实现。
CPUID指令
这可能是我没有遵循你的推理。 对我来说,似乎有比连接器更好的地方做这种事情。 正如我所看到的 – 尽管在你的情况下可能不是这样 – 程序通常是建立在一台机器上,并在另一台机器上执行。 出于这个原因,构建机的CPU通常是不重要的。
如果您针对具有特定功能的CPU,那么您可能希望利用该CPU的通用编译器优化。 由于这个原因,为所有(或一个特定的)CPU编译的应用程序模块,与CPU特定的库链接,似乎并不理想。 可以使用命令行#defines使编译器将func3重命名为相应的运行时函数。 这将导致链接器产生一个更小的程序或引用更少的DLL(如果你的库放在那里)。
如前所述,测试也可以在运行时进行。 如果func3是最初指向某个(通用)函数的指针,则可以根据您对CPUID的检查结果来覆盖它。这将在初始化期间发生,并且在随后的执行过程中没有不利的性能影响。 缺点是.exe会更大,因为它需要包含函数的所有可用变体。
要实际执行链接,我们建议您编写一个小的命令行程序,返回一个退出代码,该代码取决于程序检测到的CPU。 然后在make文件中解释此退出代码以选择(复制)适当的库。