gcc(6.1.0)使用'错误' SSE内在函数中的说明

时间:2016-11-17 17:44:09

标签: c gcc sse intrinsics

背景:我开发了一个用C / C ++编写的计算密集型工具,它必须能够在各种不同的x86_64处理器上运行。为了加速浮点和整数的计算,代码包含了很多SSE *内在函数,它们具有针对不同CPU SSE功能定制的不同路径。 (由于在程序开始时检测到CPU标志并用于设置布尔值,我假设对定制的代码块的分支预测将非常有效地工作)。

为简单起见,我认为只需要考虑SSE2到SSE4.2。

为了访问SSE4.2内在函数fpr的4.2路径,我需要使用gcc' s -msse4.2选项。

问题 我遇到的问题是,至少使用6.1.0,gcc使用 sse4.2 指令执行 sse2 内在函数mm_cvtsi32_si128,pinsrd

如果我使用-msse2限制编译,它将使用sse2指令,movd,即。英特尔"内在指导"说它应该使用。

这令两个方面很烦人。

1)关键问题是当程序在pre4.2 CPU上运行时,程序现在会因非法指令而崩溃。我无法控制使用何种硬件,因此可执行文件需要与旧机器兼容,但需要利用新硬件上的功能。

2)根据英特尔内在指南,pinrd指令比它取代的mov要慢很多。 (pinsrd更通用但不需要这样做。)

有没有人知道如何让gcc 只是使用内在指南所说的应该使用的指令,但仍允许在同一个编译单元中通过SSE4 *访问所有SSE2?

更新:我还应该注意,在Linux,Windows和OSX下使用各种不同的编译器编译相同的代码,所以如果可能的话,我们宁愿避免或至少拥有最少的编译器特定扩展。

Update2 :(感谢@PeterCordes)似乎如果启用了优化,gcc将在适当的时候恢复使用来自pinsrd的movd。

1 个答案:

答案 0 :(得分:5)

如果在编译步骤中将-msse4.2标志赋予gcc的命令行,它将假定它可以自由地用于整个翻译单元的SSE 4.2指令集。这可能会导致您描述的行为。如果您需要使用SSE2及以下代码的代码,则需要使用-msse2(如果您正在为x86_64构建,则根本不使用任何标记)。

我能想到的一些选择是:

  • 如果您可以在功能级别轻松分解代码,那么gcc的multiversioning功能可以提供帮助。它需要一个相对较新版本的编译器,但它允许你做这样的事情(取自上面的链接):

     __attribute__ ((target ("default")))
     int foo ()
     {
       // The default version of foo.
       return 0;
     }
    
     __attribute__ ((target ("sse4.2")))
     int foo ()
     {
       // foo version for SSE4.2
       return 1;
     }
    
     __attribute__ ((target ("arch=atom")))
     int foo ()
     {
       // foo version for the Intel ATOM processor
       return 2;
     }
    
     __attribute__ ((target ("arch=amdfam10")))
     int foo ()
     {
       // foo version for the AMD Family 0x10 processors.
       return 3;
     }
    
     int main ()
     {
       int (*p)() = &foo;
       assert ((*p) () == foo ());
       return 0;
     }
    

    在这个例子中,gcc将自动编译foo()的不同版本,并根据CPU的功能在运行时调度到适当的版本。

  • 您可以将不同的实现(SSE2,SSE4.2等)分解为不同的翻译单元,然后在运行时适当地调度到正确的实现。

  • 您可以将所有SIMD代码放入共享库,并使用不同的编译器标志多次构建共享库。然后在运行时,您可以检测CPU的功能并加载适当版本的共享库。这是Intel's Math Kernel Library等图书馆采用的方法。