Итак, я посмотрел на магию, которая есть O3
в GCC (ну, собственно, я собираюсь использовать Clang, но это то же самое с GCC, и я предполагаю, что большая часть оптимизатора была вытащена от GCC до Clang).
Рассмотрим эту программу на C:
int foo(int n) {
if (n == 0) return 1;
return n * foo(n-1);
}
int main() {
return foo(10);
}
Первое, что я был довольно WOW-ed в (который был также WOW-ed в этом вопросе - qaru.site/info/53943/...), заключалось в том, как int foo(int)
(a основная факториальная функция) компилируется в замкнутый цикл. Для этого это сборка ARM:
.globl _foo
.align 2
.code 16
.thumb_func _foo
_foo:
mov r1, r0
movs r0, #1
cbz r1, LBB0_2
LBB0_1:
muls r0, r1, r0
subs r1, #1
bne LBB0_1
LBB0_2:
bx lr
Блейми, подумал я. Это очень интересно! Полностью плотный цикл для факториала. ВАУ. Это не оптимизация хвостового вызова, так как, ну, это не хвостовой вызов. Но, похоже, он сделал очень похожую оптимизацию.
Теперь посмотрите main
:
.globl _main
.align 2
.code 16
.thumb_func _main
_main:
movw r0, #24320
movt r0, #55
bx lr
Это просто взорвало мой разум, чтобы быть честным. Он просто полностью обходит foo
и возвращает 3628800
, который равен 10!
.
Это заставляет меня понять, как ваш компилятор может часто выполнять гораздо лучшую работу, чем вы можете оптимизировать свой код. Но возникает вопрос: как ему удается сделать такую хорошую работу? Итак, кто-нибудь может объяснить (возможно, связав соответствующий код), как работают следующие оптимизации:
-
Начальная оптимизация
foo
должна быть замкнутой петлей. -
Оптимизация, где
main
просто идет и возвращает результат напрямую, а не выполняетfoo
.
Еще один интересный побочный эффект этого вопроса состоял бы в том, чтобы показать еще несколько интересных оптимизаций, которые может сделать GCC/Clang.