Хотя -Os
(«оптимизировать по размеру»), как ожидается, будет производить код более компактный по сравнению с кодом, созданным с опциями -O1
, -O2
и -O3
(«оптимизация по скорости»), на самом деле неттакая гарантия, как прокомментировал @Robert Harvey.
Оптимизация компиляции - очень сложный и деликатный процесс.Он состоит из десятков различных этапов оптимизации, которые обычно выполняются последовательно: каждый этап оптимизации выполняет свою работу над представлением дерева программ и подготавливает почву для следующего этапа.Во время процесса оптимизации каждое решение, принятое на одном этапе, может оказать влияние на оптимизацию в будущем, и проходы могут взаимодействовать нетривиальным образом, что может быть очень трудно предсказать.Компилятор использует разные эвристики для создания наиболее оптимального кода, но в некоторых случаях эти эвристики не работают, как в этом случае.
В этом примере кажется, что все начинается как ожидалось - с производством -Os
более компактный промежуточный код, но это изменится позже.Одной из первых фаз, которые должны быть выполнены GCC, является фаза Expand , которая переводит представление дерева высокого уровня GCC, называемое GIMPLE, в представление RTL более низкого уровня.Он производит код RTL, подобный следующему:
O3:
tmp1
<- <code>x tmp2
<-<code>tmp1 << 2 tmp3
<- <code>tmp2 + x retval
<- <code>tmp3 + 6
Os:
tmp
<- <code>x * 5 tmp2
<- <code>tmp + 6 retval
<- <code>tmp2
Пока все хорошо - -Os
выигрывает.Но после этого, примерно через 15 фаз, выполняется фаза Combine , которая пытается объединить последовательность инструкций в одну инструкцию.Для кода -O3
Combine может очень хитро свернуть его до инструкции leaq
в конечном выводе, но для -Os
, Combine не работает какмного хорошего, и не в состоянии свернуть код дальше.С этого момента код не сильно меняется при дальнейшей оптимизации.
Чтобы ответить на точный вопрос - почему GCC делает это (сгенерируйте код, который он делает во время Expand с помощью -O3
, и почему Объединение не делает лучше в -Os
), нужно изучить код GCC и выяснить, какие параметры GCC являются важными, а также решения, принятые на предыдущих этапах оптимизации.
Но дело в том, что, хотя GCC и выполняется в этом примере, он может быть лучшим выбором для большинства других примеров.Это вопрос деликатных компромиссов - нелегкая работа для авторов компиляторов!
Это может не полностью ответить на вопрос, но, надеюсь, даст полезную информацию.Если вы заинтересованы в проверке выходных данных GCC на каждом этапе оптимизации, вы можете добавить флаг компиляции -da
, который будет генерировать аннотированные дампы деревьев для каждого этапа, и флаг -dP
, который добавляет аннотации к сгенерированным деревьям.вывод сборки вместе с -S
.