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

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

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

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

Ответ 1

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

#include <stdio.h>

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 Выполнение программы, в котором говорится:

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

push "%i"
push 42
call printf 
ret

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

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

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

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

int result = func1() + func2();

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