2011-02-15 88 views
11

我想找出一个有效的方法来加载编译时间常量浮点到SSE(2/3)寄存器。我试过这样简单的代码,加载常量浮点到SSE寄存器

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

但是,它会从内存中产生4条movss指令!

movss  xmm0,dword ptr [[email protected] (14048E534h)] 
movss  xmm1,dword ptr [[email protected] (14048E530h)] 
movaps  xmm6,xmm12 
shufps  xmm6,xmm12,0C6h 
movss  dword ptr [rsp],xmm0 
movss  xmm0,dword ptr [[email protected] (14048E52Ch)] 
movss  dword ptr [rsp+4],xmm1 
movss  xmm1,dword ptr [[email protected] (14048E528h)] 

其装入标量和流出的记忆......(?!?!)

这样做虽然..

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

产生。

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

理想的情况下,这将是很好,如果我有常数他们将是一个办法不连碰内存,只是立即样式指令做(例如编入指令本身的常量)。

由于

+2

对于高性能的SSE/2代码,我强烈建议使用GCC/ICC。阅读此更多信息为什么 - http://www.liranuna.com/sse-intrinsics-optimizations-in-popular-compilers/ – LiraNuna 2011-03-07 21:43:54

回答

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 
2

通常常量如这将任何环或代码的“热”部件之前被加载,所以性能不应该是很重要的。但是如果你不能避免在循环中做这种事情,那么我会先试着_mm_set_ps,看看会产生什么。也可以尝试ICC而不是gcc,因为它倾向于生成更好的代码。

+0

我正在使用visual studio和_mm_set_ps正在生成更多movss。我认为visual studio编译器非常糟糕。 – coderdave 2011-02-15 19:49:06

+2

@coderdave:是的Visual Studio生成非常糟糕的SSE代码 - 这也是一个痛苦的SSE使用,因为它有各种愚蠢的ABI限制和其他烦恼 - 使用海湾合作委员会或更好的ICC,如果你可以 – 2011-02-15 20:29:19

3

首先,你在编译什么优化级别?在-O0或-O1上看到这种代码的情况并不少见,但是在大多数编译器中看到-O2或更高的代码,我会很惊讶。

其次,SSE没有立即加载。你可以做一个负载立即到GPR,则该值转移到SSE,但你不能没有实际负载召唤其他值(忽略像0(int)-1,可通过逻辑运算产生某些特殊的价值。

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

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中非常有用,但大多数pcs上的大型缓存可能会使这个对sse不太有用。

Agner Fog's Optimization Manual,book 2,section 13.4,http://www.agner.org/optimize/有很好的讨论。

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