将常量浮点数加载到 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 寄存器中的主要内容,如果未能解决你的问题,请参考以下文章

python中浮点数的处理

shell中浮点数运算

Python中浮点数的二进制表示(位不是十六进制)

c语言中浮点数四舍五入 。 保留一个浮点数小数点后的6位,第3位要四舍五入。如 1.1234.567到1234.570000

python 中浮点数四舍五入的问题

Python 中浮点数四舍五入的问题