动态链接器中的运行时cpu检测(ld.so)

时间:2011-12-06 17:12:20

标签: c linux optimization linker

我想将运行时CPU Dispatch集成到我的库中。我有几个版本的功能,针对sse2 / sse3 / avx和x87通用变体进行了优化。我想将所有版本编译成单个.so库,我想如何实现一个cpu调度程序。

我认为最快的方法是在链接步骤(动态链接)中获取cpu调度,所以当ld.so将加载我的库时,我希望它检查,cpu是否支持sse2,sse3或avx,然后我希望ld.so选择正确的功能集。

例如(使用gcc target attribute):

库:

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_genericfunc3_sse2func3_sse3func3_avx。所以,如果cpu是Core i7-xxxx,我希望每次调用func3都会调用func3_avx,如果cpu是PentiumPro,调用func3将调用func3_generic。

同时我不想手动编写大量的调度代码,我希望以最小的开销选择正确的变量(没有额外的间接跳转)。这意味着我可以在应用程序启动时花费额外的时间,但在调用此函数时没有任何额外的时间(在某些情况下会有非常多的调用)。

更新。链接器可以根据AUXV向量AT_HWCAP:字段:

进行调度
$ LD_SHOW_AUXV=1 /bin/echo
...
AT_HWCAP:    fpu ... mmx fxsr sse sse2

3 个答案:

答案 0 :(得分:1)

...使用.so简单地加载其中一个dlopen是否可以接受?您可以使用各种方法查询CPU类型,然后选择适当的库来绑定func3

答案 1 :(得分:-1)

如何使用函数指针数组,然后使用CPUID指令让它们在启动时指向特定的实现。在启动时花费的周期数应该是微不足道的。

如果你真的想避免任何启动成本,另一种方法是编写另一个小程序来查询CPU的功能,然后为gcc构造一个命令行,并定义一些宏,以便只编译某些函数实现。

CPUID instruction

答案 2 :(得分:-1)

可能是因为我没有按照你的推理。对我而言,似乎有更好的地方来做这种事情而不是链接器。正如我所看到的 - 虽然在你的情况下可能不是这样 - 程序通常建立在一台机器上并在另一台机器上执行。因此,构建机器的CPU通常不重要。

如果您的目标是具有特定功能的CPU,那么您可能希望利用该CPU的通用编译器优化。因此,为所有(或一个特定的)CPU编译的应用程序模块似乎不是最佳的,这些CPU与特定于CPU的库链接。命令行#defines可用于使编译器将func3重命名为适当的运行时函数。这将导致链接器生成一个较小的程序或引用较少的dll(如果你的lib放在那里)。

测试也可以在运行时执行,如前所述。如果func3是一个最初指向某个(通用)函数的指针,则可以根据对CPUID的检查结果覆盖它。在初始化期间会发生此开销,并且在后续执行期间不会产生不利的性能影响。缺点是.exe会更大,因为它需要包含函数的所有可用变体。

要实际执行链接,您需要我建议您编写一个小命令行程序,它返回一个取决于程序检测到的CPU的退出代码。然后在make文件中解释此退出代码以选择(复制)相应的库。