我正在尝试通过研究使用-O3优化在
gcc中编译的简单C代码来学习矢量化.更具体地说,编译器的矢量化程度如何.这是一个个人的旅程,能够通过更复杂的计算来验证gcc -O3的性能.我理解传统的观点是编译器比人更好,但我从不认为这种智慧是理所当然的.
然而,在我的第一个简单测试中,我发现gcc的一些选择很奇怪,而且老实说,在优化方面非常疏忽.我愿意假设有一些编译器是有目的的,并且知道关于cpu的一些事情(在这种情况下是Intel i5-2557M),我不知道.但我需要知识渊博的人做一些确认.
我的简单测试代码(段)是:
int i; float a[100]; for (i=0;i<100;i++) a[i]= (float) i*i;
与for循环对应的结果汇编代码(段)如下:
.L6: ; loop starts here movdqa xmm0,xmm1 ; copy packed integers in xmm1 to xmm0 .L3: movdqa xmm1,xmm0 ; wait,what!? WHY!? this is redundant. cvtdq2ps xmm0,xmm0 ; convert integers to float add rax,16 ; increment memory pointer for next iteration mulps xmm0,xmm0 ; pack square all integers in xmm0 paddd xmm1,xmm2 ; pack increment all integers by 4 movaps XMMWORD PTR [rax-16],xmm0 ; store result cmp rax,rdx ; test loop termination jne .L6
我理解所有步骤,并且在计算上,所有步骤都是有道理的.但是,我不明白,gcc选择在迭代循环中加入一个步骤,在xmm0加载xmm1之后立即加载xmm1和xmm0.即
.L6 movdqa xmm0,xmm1 ; loop starts here .L3 movdqa xmm1,xmm0 ; grrr!
仅这一点让我质疑优化器的健全性.显然,额外的MOVDQA不会干扰数据,但从面值来看,gcc似乎非常疏忽.
在汇编代码(未示出)的早期,xmm0和xmm2被初始化为对于矢量化有意义的某个值,因此很明显,在循环开始时,代码必须跳过第一个MOVDQA.但是为什么gcc不会简单地重新排列,如下所示.
.L3 movdqa xmm1,xmm0 ; initialize xmm1 PRIOR to loop .L6 movdqa xmm0,xmm1 ; loop starts here
或者甚至更好,只需初始化xmm1而不是xmm0并完全转储MOVDQA xmm1,xmm0步骤!
我准备相信cpu足够聪明,可以跳过冗余步骤或类似的东西,但是如果它能够完全优化复杂的代码,如果它甚至可以得到这么简单的代码呢?或者有人能提供一个合理的解释,让我相信gcc -O3是好东西吗?
解决方法
我不是100%肯定,但看起来你的循环通过将其转换为浮动来破坏xmm0,所以你要在xmm1中得到整数值,然后复制到另一个寄存器(在这种情况下是xmm0).
虽然已知编译器有时会发出不必要的指令,但在这种情况下,我无法真正看到这种情况.
如果你想让xmm0(或xmm1)保持整数,那么对于i的第一个值,不要使用float的强制转换.也许你想要做的是:
for (i=0;i<100;i++) a[i]= (float)(i*i);
但另一方面,gcc 4.9.2似乎没有这样做:
g++ -S -O3 floop.cpp .L2: cvtdq2ps %xmm1,%xmm0 mulps %xmm0,%xmm0 addq $16,%rax paddd %xmm2,%xmm1 movaps %xmm0,-16(%rax) cmpq %rbp,%rax jne .L2
clang也不是(大约3周前的3.7.0)
clang++ -S -O3 floop.cpp movdqa .LCPI0_0(%rip),%xmm0 # xmm0 = [0,1,2,3] xorl %eax,%eax .align 16,0x90 .LBB0_1: # %vector.body # =>This Inner Loop Header: Depth=1 movd %eax,%xmm1 pshufd $0,%xmm1,%xmm1 # xmm1 = xmm1[0,0] paddd %xmm0,%xmm1 cvtdq2ps %xmm1,%xmm1 mulps %xmm1,%xmm1 movaps %xmm1,(%rsp,%rax,4) addq $4,%rax cmpq $100,%rax jne .LBB0_1
我编译的代码:
extern int printf(const char *,...); int main() { int i; float a[100]; for (i=0;i<100;i++) a[i]= (float) i*i; for (i=0; i < 100; i++) printf("%f\n",a[i]); }