Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

Удаление бесполезного кода компилятором

#cpp #оптимизация #компиляция


Рассмотрим простой код.

void f() {
}

// ...

f();


Ясно, что функция f не делает ничего (точнее, не делает никакой полезной работы),
поэтому компилятор может её спокойно "выбросить" без изменения поведения программы.
(Вариант обращения к памяти за вершиной стека чтобы "сцапать" оттуда адрес возврата
не берём, ибо "грязный хак", да ещё может и не сработать.)

Усложним задачу:

void g(int x) {
}

int calc_x() {
    // Долго и упорно что-то вычисляем.
}

// ...

g(42);        // Ни на что не влияет.
g(calc_x());  // А вот тут непонятно...


Ясно, что g(42) можно выкинуть. Но выкинуть g(calc_x()) в общем случае нельзя, поскольку
у calc_x могут быть побочные эффекты.

Соответственно, вопрос: при каких условиях компилятор будет иметь право выбрасывать
вызовы функции g?

P.S. Я понимаю, что компилятор может оставить даже вызов f(), если отключены все
оптимизации. Вопрос следует понимать именно так, как написано - не выбросит, а имеет
право выбросить.
    


Ответы

Ответ 1



Компилятор может превратить вызов g(calc_x()); в следующий [псевдо]код: int x = ...; calc_x код x теперь чем-то проинициализирован. Здесь мог бы быть код из g, но его там нет. Увы. Т.е. компилятор заинлайнил обе функции, после чего он смотрит, что x нигде не используется и на этом этапе он может выкинуть код вообще. Для примера, вот такой C++ код: void g(int x) { } int calc_x() { int x = 0; while(x < 500) { ++x; } return x; } int main() { g(42); g(calc_x()); } Превращается в вот такой ассемблер(и в студии, и в gcc): main: xor eax, eax ret Если попытаться вывести общую закономерность, то можно считать так: если компилятор может доказать, что код не имеет побочных эффектов, то он может его выкинуть. В противном случае он не имеет право его трогать. Простые вычисления, это код свободный от побочных эффектов, поэтому компилятор и выкидывает его, в примере выше. Но «может» не значит, что он его выкинет. Поэтому полагаться на это нельзя.

Ответ 2



Во первых, в приведенном примере компилятор выкинуть f() не может. По очень простой причине - это видимый извне символ. Что он может делать - это не вызывать f() в данной transaltion unit. Иными словами, inline into noop. Это превращает вызов g(calc_x()) в calc_x(). Теперь все зависит от содержимого calc_x(). Компилятор оценивает наличие побочных эффектов по следующим критериям - вызов третьих, непрозрачных функций, вызов прозрачных функций с побочными эффектами, вызов стандартных функций с известными побочными эффектами, модификация любых объектов кроме автоматических. Если ничего из этого не происходит, то код считается не имеющим побочных эффектов, и может быть выброшен компилятором.

Комментариев нет:

Отправить комментарий