Мы можем использовать компилятор Проводник на godbolt.org
смотреть на вашем примере. Мы будем использовать следующий код TestBench:
int test() {
int a[15] = {0};
int b[15] = {0};
for (int i = 0; i < 15; i++){
a[b[i]]++;
}
return 0;
}
Godbolt показывает сборку x86, а не LLVM байткодом, но я кратко это немного, чтобы показать, что происходит. Здесь на -O0 -m32
:
test():
# set up stack
.LBB0_1:
cmp dword ptr [ebp - 128], 15 # i < 15?
jge .LBB0_4 # no? then jump out of loop
mov eax, dword ptr [ebp - 128] # load i
mov eax, dword ptr [ebp + 4*eax - 124] # load b[i]
mov ecx, dword ptr [ebp + 4*eax - 64] # load a[b[i]]
add ecx, 1 # increment it
mov dword ptr [ebp + 4*eax - 64], ecx # store it back
mov eax, dword ptr [ebp - 128]
add eax, 1 # increment i
mov dword ptr [ebp - 128], eax
jmp .LBB0_1 # repeat
.LBB0_4:
# tear down stack
ret
Это выглядит, как мы ожидали бы, что: цикл хорошо виден, и он делает все шаги, которые мы в списке. Если мы собираем на , мы видим, что цикл еще есть, но это гораздо проще:
test(): # @test()
# set up stack
.LBB0_1:
mov ecx, dword ptr [esp + 4*eax] # load b[i]
inc dword ptr [esp + 4*ecx + 60] # increment a[b[i]]
inc eax # increment i
cmp eax, 15 # compare == 15
jne .LBB0_1 # no? then loop
# tear down stack
ret
Clang теперь использует inc
инструкция (полезный), заметил, что это можно использовать eax
регистр для счетчика i
петли (в чистом виде) , и переместил проверку состояния на дно петли (возможно, лучше). Тем не менее, мы все еще можем распознать наш оригинальный код. Теперь давайте попробуем с -O2 -m32 -march=i386
:
test():
xor eax, eax # does nothing
ret
Это все? Да.
clang
обнаружил, что массив a
никогда не может использоваться вне функции. Это означает, что приращение никогда не повлияет на какую-либо другую часть программы, а также что никто не упустит ее, когда она исчезнет.
Снятие приращения оставляет петлю for
с пустым телом и без побочных эффектов, которые также можно удалить. В свою очередь, удаление цикла оставляет пустую функцию (для всех целей и задач).
Эта пустая функция, скорее всего, вы видели в байт-коде LLVM (ret i32 0
).
Это не очень научное описание, и шаги clang
принимает могут быть разными, но я надеюсь, что пример очищает его немного. Если вы хотите, вы можете прочитать на as-if rule. Я также рекомендую поиграть по телефону : посмотрите, что происходит, когда вы перемещаете a
и b
вне функции, например.
Если вы хотите поэтапно видеть оптимизацию, попробуйте: 'clang -mllvm -print-after-all' – Joky