针对不同的目标体系结构进行编译和优化

时间:2009-08-18 20:06:43

标签: c++ optimization gcc compilation

摘要:我想利用编译器优化和处理器指令集,但仍然有一个可移植的应用程序(在不同的处理器上运行)。通常我可以编译5次,让用户选择正确的运行。

我的问题是:如何自动执行此操作,以便在运行时检测处理器并执行正确的可执行文件而无需用户选择它?


我的应用程序有很多低级数学计算。这些计算通常会持续很长时间。

我想利用尽可能多的优化,最好也是(并不总是支持)指令集。另一方面,我希望我的应用程序可移植且易于使用(因此我不想编译5个不同的版本并让用户选择)。

是否有可能编译我的代码的5个不同版本并动态运行在执行时可能的最优化版本?有5个不同的版本,我的意思是不同的指令集和不同的处理器优化。

我不关心应用程序的大小。

此刻我正在Linux上使用gcc(我的代码是用C ++编写的),但我也对英特尔编译器和编译到Windows的MinGW编译器感兴趣。

可执行文件不一定能够在不同的操作系统上运行,但理想情况下也可以自动选择32位和64位。

编辑:请明确指示如何操作,最好使用小代码示例或解释链接。从我的角度来看,我需要一个超级通用的解决方案,它适用于我后来的任何随机C ++项目。

编辑我将奖金分配给了ShuggyCoUk,他有很多指针需要注意。我本来希望在多个答案之间拆分,但这是不可能的。我还没有实现,所以问题仍然是“开放”!请继续添加和/或改进答案,即使不再给予奖励。

谢谢大家!

8 个答案:

答案 0 :(得分:16)

是的,这是可能的。将所有不同优化版本编译为具有公共入口点的不同动态库,并提供可加载和运行的可执行存根 运行时正确的库,通过入口点,取决于配置文件或其他信息。

答案 1 :(得分:6)

你能用脚本吗?

您可以使用脚本检测CPU,并动态加载针对体系结构进行了最优化的可执行文件。它也可以选择32/64位版本。

如果您使用的是Linux,则可以使用

查询cpu
cat /proc/cpuinfo

您可以使用bash / perl / python脚本或Windows上的Windows脚本主机来执行此操作。您可能不希望强制用户安装脚本引擎。一个在开箱即用的操作系统上工作的恕我直言,最好。

事实上,在Windows上你可能想要编写一个小型的C#应用​​程序,这样你就可以更轻松地查询架构了。 C#app可以生成最快的任何可执行文件。

或者,您可以将不同版本的代码放在dll或共享对象中,然后根据检测到的体系结构动态加载它们。只要它们具有相同的呼叫签名,它就应该有效。

答案 2 :(得分:5)

查看liboil:http://liboil.freedesktop.org/wiki/。它可以在运行时动态选择多媒体相关计算的实现。您可能会发现自己可以自行搜索,而不仅仅是技术。

答案 3 :(得分:5)

如果您希望在Windows上干净地工作并充分利用附加的64位平台1.寻址空间和2.寄存器(可能对您更有用),您必须至少有一个单独的进程64位的。

您可以通过使用相关PE64标头的单独可执行文件来实现此目的。只需使用CreateProcess就可以将其作为相关位进行启动(除非启动的可执行文件位于某个重定向位置,否则无需担心WoW64 folder redirection

鉴于Windows上的这种限制,很可能简单地“链接”到相关的可执行文件将是所有不同选项的最简单选项,以及使单个测试变得更简单。

这也意味着你的'main'可执行文件可以完全独立,具体取决于目标操作系统(因为检测cpu / OS功能本质上是非常特定于操作系统),然后执行大部分其余的操作代码作为共享对象/ dll。 如果您当前不认为使用不同功能有任何意义,您也可以“共享”两个不同体系结构的相同文件。

我建议主要的可执行文件能够被强制做出特定的选择,这样你就可以看到在更强大的机器上使用'较小'版本会发生什么(或者如果你尝试不同的东西会出现什么错误)。 / p>

给出这个模型的其他可能性是:

  • 静态链接到标准运行时的不同版本(对于具有/不具有线程安全性的运行时),如果在没有任何SMP / SMT功能的情况下运行,则适当地使用它们。
  • 检测是否存在多个核心以及它们是真实线程还是超线程(在这些情况下操作系统是否有效地了解计划)
  • 检查诸如系统计时器/高性能计时器之类的性能并使用针对此行为优化的代码,例如,如果您执行任何需要等待一段时间才能到期的事情,从而可以了解最佳粒度。< / LI>
  • 如果您希望根据缓存大小/框上的其他负载优化您的代码选择。如果您使用的是展开的循环,那么更积极的展开选项可能取决于具有一定数量的1/2级缓存。
  • 根据体系结构有条件地编译以使用双精度/浮点数。在intel硬件上不那么重要但是如果你的目标是某些ARM cpu,有些则具有实际的浮点硬件支持,而其他则需要仿真。即使只使用条件编译而不是使用优化编译器(1),最佳代码也会发生很大变化。
  • 使用具有CUDA功能的图形卡等协处理器硬件。
  • 检测虚拟化并改变行为(可能试图避免文件系统写入)

对于正在进行这项检查,您有几个选项,最有用的选项是英特尔作为cpuid指令。

或者,使用有关所需功能的可用文档重新实现/更新现有文档。

有很多单独的文档可以解决如何检测事物:

你在CPU-Z库中付出的很大一部分就是有人为你做了这一切(以及涉及的令人讨厌的小问题)。


  1. 要小心这一点 - 很难在这个
  2. 上击败体面的优化编译器

答案 4 :(得分:3)

既然你提到你正在使用GCC,我假设你的代码是用C(或C ++)。

Neil Butterworth已经建议制作单独的动态库,但这需要一些非平凡的跨平台考虑因素(在Linux,Windows,OSX等上手动加载动态库是不同的,并且正确的可能需要一些时间)

一个便宜的解决方案是使用唯一名称简单地编写所有变体,并使用函数指针在运行时选择合适的变体。

我怀疑函数指针引起的额外解除引用将由您正在进行的实际工作分摊(但您需要确认)。

此外,获得不同的编译器优化可能需要不同的.c / .cpp文件,以及构建工具的一些小问题。但它可能比单独的库(它已经以某种形式或者另一种形式需要它)的整体工作要少。

答案 5 :(得分:3)

由于您没有指定是否对文件数量有限制,我提出了另一种解决方案:编译5个可执行文件,然后创建第六个可执行文件以启动相应的二进制文件。这是针对Linux的一些伪代码

int main(int argc, char* argv[])
{
    char* target_path[MAXPATH];
    char* new_argv[];
    char* specific_version = determine_name_of_specific_version();
    strcpy(target_path, "/usr/lib/myapp/versions");
    strcat(target_path, specific_version);

    /* append NULL to argv */
    new_argv = malloc(sizeof(char*)*(argc+1));
    memcpy(new_argv, argv, argc*sizeof(char*));
    new_argv[argc] = 0;
    /* optionally set new_argv[0] to target_path */

    execv(target_path, new_argv);
}

从好的方面来说,这种方法允许用户透明地提供32位和64位二进制文​​件,这与已经提出的任何库方法不同。在负面,Win32中没有execv(但在cygwin中是一个很好的模拟);在Windows上,您必须创建一个新进程,而不是重新执行当前进程。

答案 6 :(得分:1)

您提到了英特尔编译器。这很有趣,因为默认情况下它可以做这样的事情。然而,有一个问题。英特尔编译器未插入对合法SSE功能的检查。相反,他们检查了你是否有一个特定的英特尔芯片。默认情况仍然很慢。因此,AMD CPU无法获得合适的SSE优化版本。有漂浮的黑客将用适当的SSE检查取代英特尔支票。

32/64位差异将需要两个可执行文件。 ELF和PE格式都将此信息存储在exectuables标头中。默认情况下启动32位版本并不难,检查您是否在64位系统上,然后重新启动64位版本。但是在安装时创建适当的符号链接可能更容易。

答案 7 :(得分:1)

让我们将问题分解为两个组成部分。 1)创建依赖于平台的优化代码,2)在多个平台上构建。

第一个问题非常简单。将平台相关代码封装在一组函数中。为每个平台创建每个功能的不同实现。将每个实现放在自己的文件或文件集中。如果将每个平台的代码放在一个单独的目录中,那么构建系统最简单。

对于第二部分,我建议你看一下Gnu Atuotools(Automake,AutoConf和Libtool)。如果你曾经从源代码下载并构建了一个GNU程序,你就知道在运行make之前你必须运行./configure。配置脚本的目的是1)验证您的系统是否具有构建和运行程序所需的所有库和实用程序,以及2)为目标平台自定义Makefile。 Autotools是用于生成配置脚本的一组实用程序。

使用autoconf,您可以创建一些小宏来检查机器是否支持平台相关代码所需的所有CPU指令。在大多数情况下,宏已经存在,您只需将它们复制到autoconf脚本中即可。然后,automake和autoconf可以设置Makefile以引入适当的实现。

这一切对于在这里创建一个例子来说有点多了。学习需要一点时间。但文档完全在那里。甚至还有free book在线提供。该流程适用于您未来的项目。对于多平台支持,我认为这是最强大和最简单的方法。在其他答案中发布的许多建议都是Autotools处理的事情(CPU检测,静态和共享库支持),而您不必过多考虑它。您可能需要处理的唯一问题是找出Autotools是否适用于MinGW。我知道如果你能走那条路,他们就是Cygwin的一部分。