[英]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指令( 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;
}
并使用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.