将常量浮点数加载到 SSE 寄存器中
Posted
技术标签:
【中文标题】将常量浮点数加载到 SSE 寄存器中【英文标题】:Load constant floats into SSE registers 【发布时间】:2011-02-15 18:31:58 【问题描述】:我正在尝试找出一种将编译时间常数浮点数加载到 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)]
理想情况下,如果我有常量,那就太好了,它们甚至可以不触及内存,只需使用立即样式指令(例如编译到指令本身中的常量)即可。
谢谢
【问题讨论】:
对于高性能 SSE/2 代码,我强烈建议使用 GCC/ICC。阅读此内容以了解有关原因的更多信息 - liranuna.com/sse-intrinsics-optimizations-in-popular-compilers 【参考方案1】:如果你想强制它单次加载,你可以试试(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
数组中使用static
限定符即可。这将创建以下程序集:
movaps vec.7330, %xmm0
...
vec.7330:
.long 1065353216
.long 1066192077
.long 1067030938
.long 1067869798
【讨论】:
【参考方案2】:首先,您在什么优化级别进行编译?在 -O0 或 -O1 上看到这种代码生成并不少见,但在大多数编译器中看到 -O2 或更高版本时我会感到非常惊讶。
其次,SSE 中没有立即加载。您可以立即对 GPR 执行加载,然后将该值移动到 SSE,但您无法在没有实际加载的情况下生成其他值(忽略某些特殊值,例如 0
或 (int)-1
,可以通过逻辑运算生成。
最后,如果错误代码是在启用优化的情况下生成的并且位于性能关键位置,请针对您的编译器提交错误。
【讨论】:
我肯定在 -02 编译,所以看起来 Visual Studio 的代码生成很糟糕。随着我做更多的研究,这似乎是共识,大多数人不将 VC 用于 SSE,而只是使用汇编或其他编译器 @coderdave:那么请提交一个针对 VS 的错误。 MS 知道他们应该为这个问题投入资源的唯一方法是人们抱怨它。 虽然 SSE 不会立即加载(除了pxor
用于零或 pcmpeq
用于零的技巧)它会从内存中加载 _mm_load_ps()
,因此创建一个堆栈上的数组并从那里加载 SSE 寄存器。【参考方案3】:
通常情况下,此类常量会在任何循环或代码的“热”部分之前加载,因此性能应该不是那么重要。但是如果你不能避免在循环中做这种事情,那么我会先尝试_mm_set_ps
,看看会产生什么。也可以尝试使用 ICC 而不是 gcc,因为它往往会生成更好的代码。
【讨论】:
我正在使用 Visual Studio 并且 _mm_set_ps 正在生成更多 movss。我认为 Visual Studio 编译器非常糟糕。 @coderdave: 是的,Visual Studio 生成了非常糟糕的 SSE 代码 - 使用 SSE 也很痛苦,因为它有各种愚蠢的 ABI 限制和其他烦恼 - 如果可以的话,使用 gcc 或更好的 ICC 【参考方案4】:如果四个浮点常量相同,生成常量会更简单(也更快)。例如,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's Optimization Manual, book 2, section 13.4, http://www.agner.org/optimize/对此进行了很好的讨论。
最后说明,上面使用内联汇编器是 gcc 特有的,原因是允许使用未初始化的变量而不产生编译器警告。用vc,你可能需要也可能不需要先用_mm_setzero_ps()初始化变量,然后希望优化器能去掉这个。
【讨论】:
以上是关于将常量浮点数加载到 SSE 寄存器中的主要内容,如果未能解决你的问题,请参考以下文章
c语言中浮点数四舍五入 。 保留一个浮点数小数点后的6位,第3位要四舍五入。如 1.1234.567到1234.570000