我试图找出一种有效的方法将编译时常量浮点数加载到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)]
理想情况下,如果我有常量,那将是一种很好的方式,它们甚至不会触及内存,只需使用即时样式指令(例如编译到指令本身的常量)。
谢谢
如果你想强制它加载一次,你可以尝试(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
首先,你在编译什么优化级别?在-O0或-O1上看到那种代码并不罕见,但在大多数编译器中看到-O2或更高时我会感到非常惊讶。
其次,SSE没有立即加载。您可以立即加载到GPR,然后将该值移动到SSE,但是如果没有实际加载,则无法召唤其他值(忽略某些特殊值,如 0
要么 (int)-1
,可以通过逻辑运算生成。
最后,如果在启用优化并且在性能关键位置生成错误代码,请向编译器提交错误。
通常,诸如此类的常量将在代码的任何循环或“热”部分之前加载,因此性能不应该那么重要。但如果你不能避免在循环中做这种事情,那么我会尝试 _mm_set_ps
首先,看看它产生了什么。也尝试ICC而不是gcc,因为它往往会产生更好的代码。
如果四个浮点常量相同,则生成常量会更简单(也更快)。例如,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()初始化变量,然后希望优化器可以删除它。