Я немного разбираюсь в сборке x86-64, пытаясь узнать больше о различных расширениях SIMD, доступных (MMX, SSE, AVX).
Чтобы увидеть, как различные C или С++-конструкции преобразуются в машинный код GCC, я использовал 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
Однако, когда я протестировал то, что было бы сгенерировано, если бы тот же код был скомпилирован с использованием C-компилятора GCC, добавив флаги -x c
, я был очень удивлен.
Я ожидал, что аналогичные, если не идентичные, результаты, но C-компилятор, кажется, сгенерирует намного более сложный и, предположительно, намного более медленный машинный код. Полученная сборка слишком велика, чтобы вставить ее здесь полностью, но ее можно просмотреть на сайте godbolt.org, указав эту ссылку.
Ниже показан фрагмент сгенерированного кода, строки от 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]
Как вы можете видеть, этот код имеет множество сложных ходов и прыжков и в целом чувствует себя очень сложным способом выполнения простой инициализации массива.
Почему существует такая большая разница в сгенерированном коде?
Является ли компилятор GCC С++ лучше вообще при оптимизации кода, который действителен как на C, так и на С++ по сравнению с C-компилятором?