将常量浮点载入SSE寄存器

时间:2011-02-15 18:31:59

标签: assembly 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)]

理想情况下,如果我有常量它们将是一种不甚至触摸内存的方式并且只是使用直接样式指令(例如编译到指令本身的常量)这样做会很好。

由于

4 个答案:

答案 0 :(得分:6)

如果你想强迫它加载一次,你可以尝试(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))取决于您的编译器,可能会创建不执行此操作的错误代码,因此请注意并检查反汇编。

此外:
由于您一直在要求如何将常量放入代码,只需使用static数组的float限定符尝试上述内容。这会创建以下程序集:

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

答案 1 :(得分:3)

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

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

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

答案 2 :(得分:2)

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

答案 3 :(得分:2)

如果四个浮点常量相同,则生成常量会更简单(也更快)。例如,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()初始化变量,然后希望优化器可以删除它。