繁体   English   中英

GCC SSE代码优化

[英]GCC SSE code optimization

这篇文章与我几天前发布的另一篇文章密切相关。 这一次,我编写了一个简单的代码,它只添加了一对元素数组,将结果乘以另一个数组中的值并将其存储在第四个数组中,所有变量浮点数都是双精度类型。

我制作了两个版本的代码:一个是SSE指令,使用调用而另一个没有它我然后使用gcc和-O0优化级别编译它们。 我在下面写下:

// SSE VERSION

#define N 10000
#define NTIMES 100000
#include <time.h>
#include <stdio.h>
#include <xmmintrin.h>
#include <pmmintrin.h>

double a[N] __attribute__((aligned(16)));
double b[N] __attribute__((aligned(16)));
double c[N] __attribute__((aligned(16)));
double r[N] __attribute__((aligned(16)));

int main(void){
  int i, times;
  for( times = 0; times < NTIMES; times++ ){
     for( i = 0; i <N; i+= 2){ 
        __m128d mm_a = _mm_load_pd( &a[i] );  
        _mm_prefetch( &a[i+4], _MM_HINT_T0 );
        __m128d mm_b = _mm_load_pd( &b[i] );  
        _mm_prefetch( &b[i+4] , _MM_HINT_T0 );
        __m128d mm_c = _mm_load_pd( &c[i] );
        _mm_prefetch( &c[i+4] , _MM_HINT_T0 );
        __m128d mm_r;
        mm_r = _mm_add_pd( mm_a, mm_b );
        mm_a = _mm_mul_pd( mm_r , mm_c );
        _mm_store_pd( &r[i], mm_a );
      }   
   }
 }

//NO SSE VERSION
//same definitions as before
int main(void){
  int i, times;
   for( times = 0; times < NTIMES; times++ ){
     for( i = 0; i < N; i++ ){
      r[i] = (a[i]+b[i])*c[i];
    }   
  }
}

使用-O0编译它们时,如果没有特别给出-mno-sse(和其他)选项,gcc将使用XMM / MMX寄存器和SSE intstructions。 , and instructions. 我检查了为第二个代码生成的汇编代码,我注意到它使用了指令。 所以它使用SSE指令,但只使用那些使用寄存器最低部分的指令,如果我没有错的话。 and instructions, though a pretty larger assembly code was generated. 正如预期的那样,为第一个C代码生成的汇编代码使用了指令,尽管生成了相当大的汇编代码。

无论如何,据我所知,第一个代码应该获得更好的SIMD范例,因为每次迭代都会计算两个结果值。 尽管如此,第二个代码执行的操作比第一个代码快25%。 我还用单精度值进行了测试,得到了类似的结果。 这是什么原因?

GCC中的矢量化在-O3处启用。 这就是为什么在-O0 ,你只看到普通的标量SSE2指令( movsdaddsd等)。 使用GCC 4.6.1和你的第二个例子:

#define N 10000
#define NTIMES 100000

double a[N] __attribute__ ((aligned (16)));
double b[N] __attribute__ ((aligned (16)));
double c[N] __attribute__ ((aligned (16)));
double r[N] __attribute__ ((aligned (16)));

int
main (void)
{
  int i, times;
  for (times = 0; times < NTIMES; times++)
    {
      for (i = 0; i < N; ++i)
        r[i] = (a[i] + b[i]) * c[i];
    }

  return 0;
}

并使用gcc -S -O3 -msse2 sse.c进行编译,为内循环生成以下指令,这非常好:

.L3:
    movapd  a(%eax), %xmm0
    addpd   b(%eax), %xmm0
    mulpd   c(%eax), %xmm0
    movapd  %xmm0, r(%eax)
    addl    $16, %eax
    cmpl    $80000, %eax
    jne .L3

如您所见,通过启用矢量化,GCC会发出代码以并行执行两个循环迭代。 但是它可以改进 - 这段代码使用SSE寄存器的低128位,但它可以使用完整的256位YMM寄存器,通过启用SSX指令的AVX编码(如果在机器上可用)。 因此,使用gcc -S -O3 -msse2 -mavx sse.c编译相同的程序gcc -S -O3 -msse2 -mavx sse.c给出内部循环:

.L3:
    vmovapd a(%eax), %ymm0
    vaddpd  b(%eax), %ymm0, %ymm0
    vmulpd  c(%eax), %ymm0, %ymm0
    vmovapd %ymm0, r(%eax)
    addl    $32, %eax
    cmpl    $80000, %eax
    jne .L3

注意,每条指令前面的v和该指令使用256位YMM寄存器,原始循环的四次迭代是并行执行的。

我想扩展chill的答案并引起你的注意,因为GCC似乎无法在向后迭代时对AVX指令进行相同的智能使用。

只需用chill的示例代码替换内部循环:

for (i = N-1; i >= 0; --i)
    r[i] = (a[i] + b[i]) * c[i];

带选项-S -O3 -mavx GCC(4.8.4)产生:

.L5:
    vmovsd  a+79992(%rax), %xmm0
    subq    $8, %rax
    vaddsd  b+80000(%rax), %xmm0, %xmm0
    vmulsd  c+80000(%rax), %xmm0, %xmm0
    vmovsd  %xmm0, r+80000(%rax)
    cmpq    $-80000, %rax
    jne     .L5

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM