我一直在尝试学习有关各种SIMD扩展(MMX,SSE,AVX)的知识,并使用x86-64汇编进行实验。
为了查看GCC如何将不同的C或C ++结构转换为机器代码,我一直在使用Compiler Explorer这个绝妙的工具。
在我的其中一个“游玩会话”中,我想看看GCC如何优化简单的运行时整数数组初始化。 在这种情况下,我尝试将数字0到2047写入包含2048个无符号整数的数组。
代码如下:
如果我启用优化和AVX-512指令
然而,当我测试了使用GCC C编译器编译相同代码时添加
作为您可以看到的,这段代码有很多复杂的移动和跳跃,并且总体上感觉像是执行简单数组初始化的非常复杂的方法。
为什么生成的代码存在如此大的差异?
在将C和C++都有效的代码进行优化方面,GCC C++编译器是否比C编译器更好?
为了查看GCC如何将不同的C或C ++结构转换为机器代码,我一直在使用Compiler Explorer这个绝妙的工具。
在我的其中一个“游玩会话”中,我想看看GCC如何优化简单的运行时整数数组初始化。 在这种情况下,我尝试将数字0到2047写入包含2048个无符号整数的数组。
代码如下:
unsigned int buffer[2048];
void setup()
{
for (unsigned int i = 0; i < 2048; ++i)
{
buffer[i] = i;
}
}
如果我启用优化和AVX-512指令
-O3 -mavx512f -mtune=intel
,GCC 6.3会生成一些非常聪明的代码 :)setup():
mov eax, OFFSET FLAT:buffer
mov edx, OFFSET FLAT:buffer+8192
vmovdqa64 zmm0, ZMMWORD PTR .LC0[rip]
vmovdqa64 zmm1, ZMMWORD PTR .LC1[rip]
.L2:
vmovdqa64 ZMMWORD PTR [rax], zmm0
add rax, 64
cmp rdx, rax
vpaddd zmm0, zmm0, zmm1
jne .L2
ret
buffer:
.zero 8192
.LC0:
.long 0
.long 1
.long 2
.long 3
.long 4
.long 5
.long 6
.long 7
.long 8
.long 9
.long 10
.long 11
.long 12
.long 13
.long 14
.long 15
.LC1:
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
.long 16
然而,当我测试了使用GCC C编译器编译相同代码时添加
-x c
标志所生成的内容时,我感到非常惊讶。我原以为结果会类似,如果不是完全相同,但C编译器似乎生成了更加复杂和慢速的机器码。生成的汇编代码太长无法在此处粘贴,但可以通过访问godbolt.org并按照this链接查看。下面是生成代码的一部分,从第58行到83行。.L2:
vpbroadcastd zmm0, r8d
lea rsi, buffer[0+rcx*4]
vmovdqa64 zmm1, ZMMWORD PTR .LC1[rip]
vpaddd zmm0, zmm0, ZMMWORD PTR .LC0[rip]
xor ecx, ecx
.L4:
add ecx, 1
add rsi, 64
vmovdqa64 ZMMWORD PTR [rsi-64], zmm0
cmp ecx, edi
vpaddd zmm0, zmm0, zmm1
jb .L4
sub edx, r10d
cmp r9d, r10d
lea eax, [r8+r10]
je .L1
mov ecx, eax
cmp edx, 1
mov DWORD PTR buffer[0+rcx*4], eax
lea ecx, [rax+1]
je .L1
mov esi, ecx
cmp edx, 2
mov DWORD PTR buffer[0+rsi*4], ecx
lea ecx, [rax+2]
作为您可以看到的,这段代码有很多复杂的移动和跳跃,并且总体上感觉像是执行简单数组初始化的非常复杂的方法。
为什么生成的代码存在如此大的差异?
在将C和C++都有效的代码进行优化方面,GCC C++编译器是否比C编译器更好?
static unsigned int buffer[2048];
使得 C 代码类似。不过你必须实际使用buffer
,否则它会被完全消除。看起来这是一个对齐问题,额外的代码是为了处理对齐不正确的情况。 - Jestervoid g(void *); g(buffer);
将防止缓冲区被优化掉。 - M.Munsigned int buffer[2048] = { 0 };
放入代码中也会生成更简单的代码。或许Olaf确实想到了什么,在C语言中,unsigned int buffer[2048]
是一个“试探性定义”,而这在C++中是不存在的。这并不会影响程序的可观察行为,但显然它对GCC代码生成有一定影响。 - M.M