Compiler Explorer - очень полезный инструмент для просмотра сборки вашей сгенерированной программы, потому что нет другого способа выяснить, оптимизировал ли компилятор что-то или нет. Демо .
При фактическом увеличении ваш main
выглядит следующим образом:
main: # @main
push rax
test edi, edi
jle .LBB0_1
lea eax, [rdi - 1]
lea ecx, [rdi - 2]
imul rcx, rax
shr rcx
lea esi, [rcx + rdi]
add esi, 41
jmp .LBB0_3
.LBB0_1:
mov esi, 42
.LBB0_3:
mov edi, offset std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
xor eax, eax
pop rcx
ret
Как видите, компилятор полностью встроил вызов T::inc
и выполняет приращение напрямую.
Для пустого T::inc
вы получите:
main: # @main
push rax
mov edi, offset std::cout
mov esi, 42
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
xor eax, eax
pop rcx
ret
Компилятор оптимизировал весь цикл!
Верно ли мое предположение, что вызовы t.inc(...)
не влияют отрицательно на производительность, когда я указываю опцию -DNO_INC
, потому что вызов пустой функции оптимизирован?
Да.
Если мое предположение выполнено, верно ли оно и для более сложных функциональных тел (в ветви #else
)?
Нет, для некоторого определения «сложный». Компиляторы используют эвристику, чтобы определить, стоит ли включать функцию или нет, и основывают свое решение на этом и ничем другом.
Интересно, все ли переменные (a
и i
) все еще загружаются в кеш при вызове t.inc(a, i)
(при условии, что они еще не там), хотя тело функции пусто.
Нет, как показано выше, цикл даже не существует.