问题 将常量浮点载入SSE寄存器


我试图找出一种有效的方法将编译时常量浮点数加载到SSE(2/3)寄存器中。我试过做这样简单的代码,

const __m128 x = { 1.0f, 2.0f, 3.0f, 4.0f }; 

但是从内存生成4个movss指令!

movss       xmm0,dword ptr [__real@3f800000 (14048E534h)] 
movss       xmm1,dword ptr [__real@40000000 (14048E530h)] 
movaps      xmm6,xmm12 
shufps      xmm6,xmm12,0C6h 
movss       dword ptr [rsp],xmm0 
movss       xmm0,dword ptr [__real@40400000 (14048E52Ch)] 
movss       dword ptr [rsp+4],xmm1 
movss       xmm1,dword ptr [__real@40a00000 (14048E528h)] 

它将标量加载到内存中......(?!?!)

这样做虽然..

float Align(16) myfloat4[4] = { 1.0f, 2.0f, 3.0f, 4.0f, }; // out in global scope

产生。

movaps      xmm5,xmmword ptr [::myarray4 (140512050h)]

理想情况下,如果我有常量,那将是一种很好的方式,它们甚至不会触及内存,只需使用即时样式指令(例如编译到指令本身的常量)。

谢谢


9339
2018-02-15 18:31


起源

对于高性能SSE / 2代码,我强烈建议使用GCC / ICC。阅读本文以获取有关原因的更多信息 liranuna.com/sse-intrinsics-optimizations-in-popular-compilers - LiraNuna


答案:


如果你想强制它加载一次,你可以尝试(gcc):

__attribute__((aligned(16))) float vec[4] = { 1.0f, 1.1f, 1.2f, 1.3f };
__m128 v = _mm_load_ps(vec); // edit by sor: removed the "&" cause its already an address

如果您有Visual C ++,请使用 __declspec(align(16)) 请求适当的约束。

在我的系统上,这个(用。编译) gcc -m32 -msse -O2;没有优化所有杂乱的代码,但仍然保留单一 movaps 最后)创建以下汇编代码(gcc / AT&T语法):

    andl    $-16, %esp
    subl    $16, %esp
    movl    $0x3f800000, (%esp)
    movl    $0x3f8ccccd, 4(%esp)
    movl    $0x3f99999a, 8(%esp)
    movl    $0x3fa66666, 12(%esp)
    movaps  (%esp), %xmm0

请注意,它在分配堆栈空间并将常量放入其中之前对齐堆栈指针。离开了 __attribute__((aligned)) out可能会根据您的编译器创建不正确的代码而不执行此操作,因此请注意,并检查反汇编。

另外:
既然你一直在要求 如何将常量放入代码中,只需尝试上面的一个 static 资格赛 float 阵列。这会创建以下程序集:

    movaps  vec.7330, %xmm0
    ...
vec.7330:
    .long   1065353216
    .long   1066192077
    .long   1067030938
    .long   1067869798

6
2018-02-16 17:27





首先,你在编译什么优化级别?在-O0或-O1上看到那种代码并不罕见,但在大多数编译器中看到-O2或更高时我会感到非常惊讶。

其次,SSE没有立即加载。您可以立即加载到GPR,然后将该值移动到SSE,但是如果没有实际加载,则无法召唤其他值(忽略某些特殊值,如 0 要么 (int)-1,可以通过逻辑运算生成。

最后,如果在启用优化并且在性能关键位置生成错误代码,请向编译器提交错误。


3
2018-02-15 19:30



我肯定在-02编译,所以看起来Visual Studio的代码生成很糟糕。当我做更多研究时,似乎这是共识,大多数人不使用VC进行SSE,只使用汇编或其他编译器 - coderdave
@coderdave:请提交针对VS的错误。 MS知道他们应该为这个问题投入资源的唯一方法就是人们会抱怨它。 - Stephen Canon
虽然SSE不会立即加载(除了像 pxor 为零或 pcmpeq 对于那些)它确实从内存加载, _mm_load_ps()因此,在堆栈上创建一个数组并从那里加载SSE寄存器没有任何问题。 - FrankH.


通常,诸如此类的常量将在代码的任何循环或“热”部分之前加载,因此性能不应该那么重要。但如果你不能避免在循环中做这种事情,那么我会尝试 _mm_set_ps 首先,看看它产生了什么。也尝试ICC而不是gcc,因为它往往会产生更好的代码。


2
2018-02-15 18:53



我正在使用visual studio,_mm_set_ps正在产生更多的movss。我认为visual studio编译器非常糟糕。 - coderdave
@coderdave:是的,Visual Studio会生成相当糟糕的SSE代码 - 这对SSE来说也很痛苦,因为它有各种愚蠢的ABI限制和其他烦恼 - 如果可以的话,使用gcc或更好的ICC - Paul R


如果四个浮点常量相同,则生成常量会更简单(也更快)。例如,1.f的位模式是0x3f800000。一种方法可以使用SSE2生成

        register __m128i onef;
        __asm__ ( "pcmpeqb %0, %0" : "=x" ( onef ) );
        onef = _mm_slli_epi32( onef, 25 );
        onef = _mm_srli_epi32( onef, 2 );

SSE4.1的另一种方法是,

        register uint32_t t = 0x3f800000;
        register __m128 onef;
        __asm__ ( "pinsrd %0, %1, 0" : "=x" ( onef ) : "r" ( t ) );
        onef = _mm_shuffle_epi32( onef, 0 );

请注意,如果此版本比SSE2版本快,我没有可能,没有对其进行分析,只测试结果是否正确。

如果四个浮点数中的每一个的值必须不同,则可以生成每个常数并将其混洗或混合在一起。

这是否有用取决于是否可能发生缓存未命中,否则从内存加载常量会更快。像这样的技巧在vmx / altivec中非常有用,但是大多数pc上的大缓存可能会使这对sse不太有用。

在Agner Fog的优化手册第2册第13.4节中对此进行了很好的讨论, http://www.agner.org/optimize/

最后注意,上面使用内联汇编程序是gcc特有的,原因是允许使用未初始化的变量而不生成编译器警告。使用vc,您可能需要首先使用_mm_setzero_ps()初始化变量,然后希望优化器可以删除它。


2
2018-02-17 12:57