我正在尝试通过研究使用-gcc编译的简单C代码并进行-O3优化来学习向量化。更具体地说,我想了解编译器的向量化效果如何。这是一个个人的学习之旅,旨在通过更复杂的计算验证gcc -O3的性能。我知道传统智慧认为编译器比人更好,但我从不把这样的智慧视为理所当然。
然而,在我的第一个简单测试中,我发现gcc做出的一些选择相当奇怪,并且在优化方面非常疏忽。我愿意假设编译器有一些目的,并且知道关于CPU(在这种情况下是Intel i5-2557M)的一些东西,而我不知道。但我需要一些有见识的人的确认。
我的简单测试代码(片段)如下:
这让我怀疑优化器的理智。显然,额外的 MOVDQA 不会干扰数据,但乍一看,gcc的做法似乎极其疏忽。
在汇编代码中的前面部分(未展示),xmm0和xmm2被初始化为在向量化方面具有意义的某些值,所以很明显,在循环开始时,代码必须跳过第一个 MOVDQA。但是,为什么 gcc 不直接像下面展示的那样重新排列呢?
甚至更好的方法是直接初始化xmm1,而不是xmm0,然后完全跳过MOVDQA xmm1,xmm0步骤!我相信CPU足够聪明,可以跳过冗余步骤,但如果它连这么简单的代码都无法正确优化,我怎么能相信gcc可以完全优化复杂的代码呢?或者有人能提供一个合理的解释,让我相信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可以完全优化复杂的代码呢?或者有人能提供一个合理的解释,让我相信gcc -O3是好东西吗?