【问题标题】:Runtime cpu detection in dynamic linker (ld.so)动态链接器 (ld.so) 中的运行时 CPU 检测
【发布时间】:2012-01-14 06:59:19
【问题描述】:

我想将运行时 CPU 调度集成到我的库中。我有一些功能的几个版本,针对 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。

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

更新。 Linker可以根据AUXV向量进行调度,AT_HWCAP:字段:

$ LD_SHOW_AUXV=1 /bin/echo
...
AT_HWCAP:    fpu ... mmx fxsr sse sse2

【问题讨论】:

  • IBM 的 Power Platform 好文章,ibm.com/developerworks/wikis/display/LinuxP/Optimized+Libraries 和 gnu_indirect_function(或 STT_GNU_IFUNC)看起来很有前途
  • 是的,这就是 STT_IFUNC 的用途(顺便说一句,这似乎是更改 Linux 的 ELF 可执行文件的 ABI 字段所需的第一个扩展)。
  • ninjalj,您能否添加 STT_IFUNC 作为答案,并简单说明它的工作原理以及为什么需要更改 ABI Fieldo?

标签: c linux optimization linker


【解决方案1】:

可能是我没有遵循你的推理。对我来说,似乎有比链接器更好的地方来做这种事情。正如我所看到的 - 尽管在您的情况下可能并非如此 - 程序通常构建在一台机器上并在另一台机器上执行。因此,构建机器的 CPU 通常并不重要。

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

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

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

【讨论】:

  • If func3 is a pointer 不是指针;它是图书馆的功能。该库将在各种 PC 上使用。我应该保留库接口,所以我不能让 func3 成为一个指针。另外,我不是在讨论库构建时的链接步骤(ld 命令),而是关于运行时链接器(ld.so)。这是动态库加载的时间,就在应用程序启动的时候。我不想有 10 或 20 个预编译版本的库,因为库中并非每个函数都有 cpu-tuned 版本。
  • 如果您的应用程序引用所有函数变体(例如在指针向量中,因此静态链接器不会优化它们)您的应用程序可以通过 CPUID(例如)确定应该使用哪个变体并将对它的引用放在 func3 可以访问的指针变量中。或者让 func3 通过开关 (C) 引用变体,其中适当的索引在启动时使用 CPUID 确定。
【解决方案2】:

... 使用dlopen 简单地加载几个.so 之一是否可以接受?您可以通过多种方法查询CPU类型,然后选择合适的库绑定func3

【讨论】:

【解决方案3】:

函数指针数组如何,然后使用 CPUID 指令让它们在启动时指向特定的实现。在启动时执行此操作所需的周期数应该很少。

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

CPUID instruction

【讨论】:

  • “函数指针数组”不是库的方法。我需要像正常一样导出函数。
  • 我相信@James 的意思是让func3() 类似于static (float* fnPtr)(float*,float*) = NULL; if (NULL == fnPtr) fnPtr = set_up_pointer(); return *fnPtr(a,b); ......但这会是你提到的额外间接跳转吗?
  • static (float* fnPtr) 与通常的库接口不兼容。
猜你喜欢
  • 2013-12-05
  • 2014-01-01
  • 1970-01-01
  • 2011-01-04
  • 2020-05-07
  • 2014-12-18
  • 1970-01-01
  • 2011-11-29
  • 2013-08-31
相关资源
最近更新 更多