2015-02-23 105 views
2

你好的StackOverflow社区在SSE寄存器(GCC,C++)

我也遇到以下挑战存储一个常数:在我的C++应用程序,我在其中相当复杂(立方)循环,在所有的深处,我执行以下:

  1. 计算4浮子由恒定
  2. 乘所有4个值转换浮子为整数

该代码将在每个循环中执行数千次迭代(导致数十亿次操作),并且我希望尽可能快地实现它,所以我试图利用SSE处理器指令。

在尝试手动优化代码时,我遇到了以下障碍:每当我将所有值乘以一个常量时,都必须将常量加载到XMM寄存器。我的想法是保留一个寄存器(并禁止编译器使用它),加载一次值,并用一个特定寄存器对乘法进行硬编码,但是我找不到正确的方法来实现这一点。

顺便说一句,可能有人请向我解释,为什么这个代码:

vmovaps .LC0(%rip), %xmm1 
    movl $1000000000, %eax 
    vmovaps .LC1(%rip), %xmm0 
    .p2align 4,,10 
    .p2align 3 
.L2: 
#APP 
# 26 "sse.cpp" 1 
    .intel_syntax noprefix; 
    mulps %xmm1,%xmm0; 
    .att_syntax prefix; 

# 0 "" 2 
#NO_APP 
    subl $1, %eax 
    jne  .L2 

执行得很差(实0m1.656s vs皇家0m1.618s)比下面的例子:

vmovaps .LC0(%rip), %xmm1 
    movl $1000000000, %eax 
    vmovaps .LC1(%rip), %xmm0 
    .p2align 4,,10 
    .p2align 3 
.L2: 
    vmulps %xmm0, %xmm1, %xmm1 
    subl $1, %eax 
    jne  .L2 

(区别在于我在gcc [第一段代码]和传统SSE指令中使用intel语法来兼容,而gcc使用AVX向量[第二代码段]自动生成版本)

+2

您应该使用内置向量支持和/或内在函数。另外,如果你想用-masm = intel的intel语法进行编译,不要试图绕过编译器的后面。至于保留一个寄存器,这也可能是一个坏主意,但gcc允许全局寄存器变量。 – Jester 2015-02-23 17:20:24

+0

如http://stackoverflow.com/a/9080351/1133179帮助中所述,Umm没有声明'const __mm128'变量? – luk32 2015-02-23 17:22:00

+0

@Jester -masm = intel打破了一些提升依赖关系,试过之前。 – Marandil 2015-02-23 21:38:47

回答

2

需要注意的是,您需要更具体地了解您如何编译内容,并可能提供最少的示例。我知道这可能不是最好的答案,但我认为它够好。它变长了,但这是因为代码。

下面的工作的底线是它应该是安全的离开编译器并使用适当的编译器标志。在底部,我举了一个例子说明如何使用本地寄存器变量,但它可能不会很有用(它很容易被忽略)。你可以使用全局寄存器变量,但它不会产生任何好结果,并且不鼓励。

我的设置是Intel(R) Core(TM) i7-4770 CPU,gcc version 4.9.2clang version 3.5.0。以下代码确实将avx_scalar存储在xmm寄存器中,其中-O1及以上。没有什么或-O0他们没有。要生成汇编代码为:

[clang++|g++] -march=native -S -Ox ./sse.cpp

其中x是优化级别。

有趣的是,与-march=archive这两个编译器决定使用SSE4.1版本超过传统SSE在任何情况下我测试,即使我在代码本身中使用传统的SSE内在函数。这很好。

我还测试了使用smmintrin.h这是SSE4.1头。没有国旗gcc使用传统的SSE和铛无法与error: "SSE4.1 instruction set not enabled"编译。通过xmmintrin.h这是传统的SSE头文件,这两个编译器在出现标志的情况下生成AVX版本,而在遗漏的时候编译器生成AVX版本。

测试代码avx.cpp

Revelvant部分g++ -march=native -S -O2 ./avx.cpp

extern "C" 
{ 
#include <smmintrin.h> 
} 

const float scalar = 3.14; 
const __m128 avx_scalar = _mm_set1_ps(scalar); 
__m128 vector; 

__m128 its_me(){ 
    __m128 ret; 
    __m128 result; 
    for(int i = 0; i < 1000; ++i) 
    { 
     vector = _mm_set_ps(i*1,i*2,i*3,i*4); 
     result = _mm_mul_ps(vector, avx_scalar); 
     ret = _mm_add_ps(ret, result); 
    } 
    return ret; 
} 

.LFB639: 
     .cfi_startproc 
     vmovaps _ZL10avx_scalar(%rip), %xmm5 
     xorl %edx, %edx 
     .p2align 4,,10 
     .p2align 3 
.L2: 
     leal (%rdx,%rdx), %ecx 
     vxorps %xmm2, %xmm2, %xmm2 
     vxorps %xmm1, %xmm1, %xmm1 
     vxorps %xmm3, %xmm3, %xmm3 
     leal 0(,%rdx,4), %eax 
     vcvtsi2ss  %ecx, %xmm3, %xmm3 
     vxorps %xmm4, %xmm4, %xmm4 
     vcvtsi2ss  %eax, %xmm2, %xmm2 
     leal (%rcx,%rdx), %eax 
     vcvtsi2ss  %edx, %xmm4, %xmm4 
     addl $1, %edx 
     vcvtsi2ss  %eax, %xmm1, %xmm1 
     vunpcklps  %xmm4, %xmm3, %xmm3 
     vunpcklps  %xmm1, %xmm2, %xmm1 
     vmovlhps  %xmm3, %xmm1, %xmm1 
     vmulps %xmm5, %xmm1, %xmm2 
     vaddps %xmm2, %xmm0, %xmm0 
     cmpl $1000, %edx 
     jne  .L2 
     vmovaps %xmm1, vector(%rip) 
     ret 
     .cfi_endproc 

而且clang++ -march=native -S -O2 ./avx.cpp

# BB#0: 
     xorl %eax, %eax 
     movl $4, %ecx 
     movl $2, %edx 
     vmovaps _ZL10avx_scalar(%rip), %xmm1 
     xorl %esi, %esi 
             # implicit-def: XMM0 
     .align 16, 0x90 
.LBB0_1:        # =>This Inner Loop Header: Depth=1 
     leal -2(%rdx), %r8d 
     leal -4(%rcx), %edi 
     vmovd %edi, %xmm2 
     vpinsrd $1, %eax, %xmm2, %xmm2 
     vpinsrd $2, %r8d, %xmm2, %xmm2 
     vpinsrd $3, %esi, %xmm2, %xmm2 
     vcvtdq2ps  %xmm2, %xmm2 
     vmulps %xmm1, %xmm2, %xmm2 
     vaddps %xmm2, %xmm0, %xmm0 
     leal 1(%rsi), %r8d 
     leal 3(%rax), %edi 
     vmovd %ecx, %xmm2 
     vpinsrd $1, %edi, %xmm2, %xmm2 
     vpinsrd $2, %edx, %xmm2, %xmm2 
     vpinsrd $3, %r8d, %xmm2, %xmm2 
     vcvtdq2ps  %xmm2, %xmm2 
     vmulps %xmm1, %xmm2, %xmm3 
     vaddps %xmm3, %xmm0, %xmm0 
     addl $2, %esi 
     addl $6, %eax 
     addl $8, %ecx 
     addl $4, %edx 
     cmpl $1000, %esi    # imm = 0x3E8 
     jne  .LBB0_1 
# BB#2: 
     vmovaps %xmm2, vector(%rip) 
     retq 

为了记录,您可以手动把本地变量到寄存器,但铛完全忽略与-01和above.I GCC鼓励寻找xmm13在输出g++ -march=native -S -Ox ./avx.cpp与下面的代码不同x值(假设你有你的CPU至少13个XMM寄存器):

extern "C" 
{ 
#include <xmmintrin.h> 
} 

const float scalar = 3.14; 

__m128 its_me(){ 
    __m128 vector; 
    register __m128 avx_scalar asm ("xmm13") = _mm_set1_ps(scalar); // that's how you do it in gcc. 
    //const __m128 avx_scalar = _mm_set1_ps(scalar); 
    __m128 ret; 
    __m128 result; 
    for(int i = 0; i < 1000; ++i) 
    { 
     vector = _mm_set_ps(i*1,i*2,i*3,i*4); 
      result = _mm_mul_ps(vector, avx_scalar); 
     ret = _mm_add_ps(ret, result); 
    } 
    return ret; 
} 
+0

如果可以避免,请不要在内部循环中使用_mm_set_ps。设置“{0.0,0.0,0.0,0.0}”一次,然后设置“_mm_add_ps”为{{1.0,2.0,3.0,4.0}}的向量来生成vector。这将在循环中用“vaddps”替换4个整数加法,'vmovd',3x'vpinsrd'和'vcvtdq2ps'。或者,如果您确实需要避免任何可能的舍入误差累积,请将其添加到整数向量中,以便在内部循环中只有“vpaddd”和“vcvtdq2ps”。 – 2015-07-10 18:57:37

+0

这是一个很好的分析,尽管对于答案来说很没用。 '__mm_set_ps'只是为了模拟“* Compute 4 float values *”,它可能很慢,而且它不是一个真正的实现。我真的没有看到优化组成部分的一点。尽管如此,我仍然从微观优化的角度发现了一个有趣的观察和建议。它当然有其价值。 – luk32 2015-07-12 18:37:29

+0

我想我没有仔细阅读代码之外的文本,以便在循环计数器上占用'mm_set_ps'作为占位符。实际上,我似乎还记得这个想法,但它对生成的代码有很大的影响,所以我仍然发布。 >< – 2015-07-12 19:20:26