GCC SSE代码优化

Gen*_*nís 14 c optimization hpc sse compiler-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];
    }   
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

chi*_*ill 15

GCC中的矢量化已启用-O3.这就是为什么在-O0,你看到的只是普通的标量SSE2指令集(movsd,addsd,等).使用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;
}
Run Code Online (Sandbox Code Playgroud)

并使用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
Run Code Online (Sandbox Code Playgroud)

如您所见,通过启用矢量化,GCC会发出代码以并行执行两个循环迭代.但是它可以改进 - 这段代码使用SSE寄存器的低128位,但它可以使用完整的256位YMM寄存器,通过启用SSX指令的AVX编码(如果在机器上可用).因此,编译相同的程序与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
Run Code Online (Sandbox Code Playgroud)

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