考虑这个简单的循环:
float f(float x[]) { float p = 1.0; for (int i = 0; i < 128; i++) p += x[i]; return p; }
如果你在gcc中用-O2 -march = haswell编译它,你会得到:
f: vmovss xmm0, DWORD PTR .LC0[rip] lea rax, [rdi+512] .L2: vaddss xmm0, xmm0, DWORD PTR [rdi] add rdi, 4 cmp rdi, rax jne .L2 ret .LC0: .long 1065353216
但是,英特尔C编译器给出:
f: xor eax, eax #3.3 pxor xmm0, xmm0 #2.11 movaps xmm7, xmm0 #2.11 movaps xmm6, xmm0 #2.11 movaps xmm5, xmm0 #2.11 movaps xmm4, xmm0 #2.11 movaps xmm3, xmm0 #2.11 movaps xmm2, xmm0 #2.11 movaps xmm1, xmm0 #2.11 ..B1.2: # Preds ..B1.2 ..B1.1 movups xmm8, XMMWORD PTR [rdi+rax*4] #4.10 movups xmm9, XMMWORD PTR [16+rdi+rax*4] #4.10 movups xmm10, XMMWORD PTR [32+rdi+rax*4] #4.10 movups xmm11, XMMWORD PTR [48+rdi+rax*4] #4.10 movups xmm12, XMMWORD PTR [64+rdi+rax*4] #4.10 movups xmm13, XMMWORD PTR [80+rdi+rax*4] #4.10 movups xmm14, XMMWORD PTR [96+rdi+rax*4] #4.10 movups xmm15, XMMWORD PTR [112+rdi+rax*4] #4.10 addps xmm0, xmm8 #4.5 addps xmm7, xmm9 #4.5 addps xmm6, xmm10 #4.5 addps xmm5, xmm11 #4.5 addps xmm4, xmm12 #4.5 addps xmm3, xmm13 #4.5 addps xmm2, xmm14 #4.5 addps xmm1, xmm15 #4.5 add rax, 32 #3.3 cmp rax, 128 #3.3 jb ..B1.2 # Prob 99% #3.3 addps xmm0, xmm7 #2.11 addps xmm6, xmm5 #2.11 addps xmm4, xmm3 #2.11 addps xmm2, xmm1 #2.11 addps xmm0, xmm6 #2.11 addps xmm4, xmm2 #2.11 addps xmm0, xmm4 #2.11 movaps xmm1, xmm0 #2.11 movhlps xmm1, xmm0 #2.11 addps xmm0, xmm1 #2.11 movaps xmm2, xmm0 #2.11 shufps xmm2, xmm0, 245 #2.11 addss xmm0, xmm2 #2.11 addss xmm0, DWORD PTR .L_2il0floatpacket.0[rip] #2.11 ret #5.10 .L_2il0floatpacket.0: .long 0x3f800000
如果我们忽略循环展开,最明显的区别是使用vaddss和icc的gcc使用addss.
这两个装配件之间是否存在性能差异?哪一个更好(忽略循环展开)?
v前缀来自VEX编码方案.看来你可以通过添加-xavx
命令行标志的一部分来获得icc来使用这些指令.但是,问题仍然存在,如果问题中的两组装配之间存在任何性能差异,或者一方是否有任何优势.
带有助记符前缀的指令v
是VEX编码指令.VEX编码方案允许编码每个SSE指令以及新的AVX指令和一些其他指令.传统指令和VEX编码指令之间几乎是1:1的对应关系,但有以下区别:
VEX编码的SSE指令隐含ymm
地将xmm
与指令中使用的寄存器操作数对应的寄存器的高128位清零.如果先前的指令在这些位中留下数据,则这避免了昂贵的部分寄存器更新.
VEX编码方案允许指令具有附加的输出操作数,而不是覆盖其中一个输入操作数.这降低了寄存器压力,并允许编译器生成更少的数据移动,从而略微提高性能.
AVX指令只能用VEX前缀编码,因为256位数据宽度不能通过任何其他方式传送.