GCC SSE代码优化

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

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

// SSE VERSION #define N 10000 #define NTIMES 100000 #include  #include  #include  #include  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。 我检查了为第二个代码生成的汇编代码,我注意到它使用了movsdaddsdmulsd指令。 所以它使用SSE指令,但只使用那些使用寄存器最低部分的指令,如果我没有错的话。 正如预期的那样,为第一个C代码生成的汇编代码使用了addpmulpd指令,尽管生成了相当大的汇编代码。

无论如何,据我所知,第一个代码应该获得更好的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