我为多个CPU编写了性能关键代码。我在运行时检测CPU,并根据检测到的CPU使用适当的函数。所以,现在我必须使用函数指针并使用这些函数指针调用函数:
void do_something_neon(void);
void do_something_armv6(void);
void (*do_something)(void);
if(cpu == NEON) {
do_something = do_something_neon;
}else{
do_something = do_something_armv6;
}
//Use function pointer:
do_something();
...
并不重要,但我会提到我已针对不同的cpu优化了功能:armv6和armv7,支持NEON。问题是通过在许多地方使用函数指针,代码变得更慢,我想避免这个问题。
基本上,在加载时,链接器使用函数地址解析relocs和patch代码。有没有办法更好地控制这种行为?
就个人而言,我提出了两种避免函数指针的方法:为cpu依赖函数创建两个独立的.so(或.dll),将它们放在不同的文件夹中,并根据检测到的CPU将这些文件夹中的一个添加到搜索中path(或LD_LIB_PATH)。加载主代码和动态链接器将从搜索路径中获取所需的dll。另一种方法是编译两个单独的库副本:) 第一种方法的缺点是它迫使我至少有3个共享对象(dll):两个用于cpu依赖函数,一个用于使用它们的主代码。我需要3,因为在加载使用这些cpu相关函数的代码之前,我必须能够进行CPU检测。关于第一种方法的好处是应用程序不需要为多个CPU加载相同代码的多个副本,它将仅加载将使用的副本。第二种方法的缺点非常明显,无需谈论它。
我想知道是否有办法不使用共享对象并在运行时手动加载它们。其中一种方法是涉及在运行时修补代码的一些hackery,它可能太复杂而无法正确完成它)。有没有更好的方法来控制加载时的重定位?也许将cpu依赖函数放在不同的部分,然后以某种方式指定哪个部分具有优先级?我认为MAC的男子气概有类似的东西。
仅限ELF(针对手臂目标)解决方案对我来说已经足够了,我并不关心PE(dll's)。
感谢
答案 0 :(得分:7)
您可能希望查找GNU动态链接器扩展STT_GNU_IFUNC
。来自Drepper的博客,当它被添加:
因此,我设计了一个ELF扩展,允许决定每个进程运行一次使用哪个实现。它使用新的ELF符号类型(STT_GNU_IFUNC)实现。只要符号查找解析为具有此类型的符号,动态链接器就不会立即返回找到的值。相反,它将值解释为函数指针,该函数指向不带参数的函数并返回要使用的实际函数指针。调用的代码可以在实现者的控制之下,并且可以根据实现者想要使用的任何信息来选择要使用的两个或更多实现中的哪一个。
来源:http://udrepper.livejournal.com/20948.html
尽管如此,正如其他人所说,我认为你误解了间接电话的性能影响。共享库中的所有代码都将通过GOT中的(隐藏)函数指针和加载/调用该函数指针的PLT条目进行调用。
答案 1 :(得分:4)
为了获得最佳性能,您需要最大限度地减少每秒间接调用(通过指针)的数量,并允许编译器更好地优化代码(DLL会妨碍这一点,因为DLL和主可执行文件之间必须有明确的边界,这个边界没有优化。)
我建议这样做:
答案 2 :(得分:2)
这是我正在寻找的确切答案。
GCC's __attribute__((ifunc("resolver")))
它需要相当近的binutils 有一篇很好的文章描述了这个扩展:Gnu support for CPU dispatching - sort of...
答案 3 :(得分:0)
Ulrich Drepper's DSO How To的第1.5.5节(2011-12-10更新)中描述了从共享库中延迟加载ELF符号。对于ARM,它在ELF for ARM的第3.1.3节中描述。
编辑:使用R提到的STT_GNU_IFUNC扩展名。我忘了这是一个扩展名。根据{{3}},GNU Binutils支持ARM,显然是自2011年3月以来。
如果你想在没有PLT间接的情况下调用函数,我建议函数指针或per-arch共享库,其中函数调用不通过PLT(注意:通过PLT调用导出的函数)。 / p>
我不会在运行时修补代码。我的意思是,你可以。您可以添加一个构建步骤:在编译后反汇编二进制文件,找到具有多个arch替代函数的函数调用的所有偏移量,构建补丁位置表,并将其链接到您的代码中。在main中,重新映射文本段可写,根据您准备的表修补偏移量,将其映射回只读,刷新指令缓存,然后继续。我相信它会奏效。您希望通过这种方法获得多少性能?我认为在运行时加载不同的共享库更容易。函数指针更容易。