Временные переменные замедляют мою программу?

Предположим, у меня есть следующий код C:

int i = 5; int j = 10; int result = i + j; 

Если я зацикливаюсь на этом много раз, было бы быстрее использовать int result = 5 + 10 ? Я часто создаю временные переменные, чтобы сделать мой код более удобочитаемым, например, если две переменные были получены из некоторого массива с использованием некоторого длинного выражения для вычисления индексов. Неужели это плохой результат в C? Как насчет других языков?

Современный оптимизирующий компилятор должен оптимизировать эти переменные, например, если мы используем следующий пример в godbolt с gcc используя -std=c99 -O3 ( см. -std=c99 -O3 прямом эфире ):

 #include  void func() { int i = 5; int j = 10; int result = i + j; printf( "%d\n", result ) ; } 

это приведет к следующему сбору:

 movl $15, %esi 

для вычисления i + j это форма постоянного распространения .

Заметьте, я добавил printf так, чтобы у нас был побочный эффект, иначе func был бы оптимизирован:

 func: rep ret 

Эти оптимизации разрешены в соответствии с правилом as-if , что требует от компилятора только эмулировать наблюдаемое поведение программы. Это описано в стандартном разделе проекта C99 5.1.2.3 Выполнение программы, в котором говорится:

В абстрактной машине все выражения оцениваются в соответствии с семантикой. Фактическая реализация не должна оценивать часть выражения, если она может вывести, что ее значение не используется и что не требуются побочные эффекты (в том числе любые вызванные вызовом функции или доступом к изменчивому объекту).

См. Также: Оптимизация кода C ++: постоянная сгибание

Это простая задача для оптимизации оптимизационного компилятора. Он удалит все переменные и заменит result на 15 .

Постоянная сворачивание в форме SSA – это в значительной степени самая простая оптимизация.

Пример, который вы дали, легко для компилятора для оптимизации. Использование локальных переменных для кэширования значений, выведенных из глобальных структур и массивов, может фактически ускорить выполнение вашего кода. Если, например, вы извлекаете что-то из сложной структуры внутри цикла for, где компилятор не может оптимизировать, и вы знаете, что значение не меняется, локальные переменные могут сэкономить довольно много времени.

Вы можете использовать GCC (другие компиляторы тоже) для создания промежуточного кода сборки и посмотреть, что делает на самом деле компилятор.

Существует обсуждение того, как включить списки сборок здесь: Использование GCC для создания удобочитаемой сборки?

Можно поучительно изучить сгенерированный код и посмотреть, что делает на самом деле компилятор.

В то время как всевозможные тривиальные различия с кодом могут нарушать поведение компилятора способами, которые слегка улучшают или ухудшают производительность, в принципе это не должно иметь какой-либо разницы в производительности, если вы используете временные переменные как это, если смысл программы не изменилось. Хороший компилятор должен генерировать одинаковый или сопоставимый код в любом случае, если вы намеренно не строите оптимизацию, чтобы получить машинный код, который как можно ближе к источнику (например, для целей отладки).

Вы испытываете ту же проблему, что и я, когда пытаюсь узнать, что делает компилятор – вы делаете тривиальную программу для демонстрации проблемы и изучаете сборку выходного файла компилятора, только чтобы понять, что компилятор оптимизировал все вы пытались заставить его покончить. Вы можете найти даже довольно сложную операцию в main (), которая сводится к существенному:

 push "%i" push 42 call printf ret 

Ваш исходный вопрос не «что происходит с int i = 5; int j = 10... ?» но «как правило, временные переменные несут временную штрафную санкцию?»

Ответ, вероятно, нет. Но вам нужно будет посмотреть на сборку для вашего конкретного, нетривиального кода. Если ваш процессор имеет много регистров, например ARM, то i и j, скорее всего, будут в регистрах, точно так же, как если бы эти регистры сохраняли возвращаемое значение функции напрямую. Например:

 int i = func1(); int j = func2(); int result = i + j; 

почти наверняка будет точно таким же машинным кодом, как:

 int result = func1() + func2(); 

Я предлагаю вам использовать временные переменные, если они упрощают понимание кода и поддерживают его, и если вы действительно пытаетесь затянуть цикл, вы все равно будете смотреть на сборку, чтобы выяснить, насколько утонченность такой производительности, как возможное. Но не жертвуйте удобочитаемостью и ремонтопригодностью на несколько наносекунд, если это не обязательно.

Давайте будем гением компьютера.