Я всегда задавался вопросом: легче ли компилятору оптимизировать функции, в которых повторно используются существующие переменные, где создаются новые (в идеале const) промежуточные переменные или где вместо создания переменных вместо прямого использования выражений?
Например, рассмотрим следующие функции:
// 1. Use expression as and when needed, no new variables
void MyFunction1(int a, int b)
{
SubFunction1(a + b);
SubFunction2(a + b);
SubFunction3(a + b);
}
// 2. Re-use existing function parameter variable to compute
// result once, and use result multiple times.
// (I've seen this approach most in old-school C code)
void MyFunction2(int a, int b)
{
a += b;
SubFunction1(a);
SubFunction2(a);
SubFunction3(a);
}
// 3. Use a new variable to compute result once,
// and use result multiple times.
void MyFunction3(int a, int b)
{
int sum = a + b;
SubFunction1(sum);
SubFunction2(sum);
SubFunction3(sum);
}
// 4. Use a new const variable to compute result once,
// and use result multiple times.
void MyFunction4(int a, int b)
{
const int sum = a + b;
SubFunction1(sum);
SubFunction2(sum);
SubFunction3(sum);
}
Моя интуиция такова:
sum будет просто помещено в регистр, и фактического доступа к базовой памяти не произойдет.a + b используется одинаковым образом для каждого вызова функции, и он должен знать, что результат a + b идентичен при каждом использовании этого выражения. Я бы по-прежнему ожидал, что результат a + b будет помещен в регистр, а не зафиксирован в памяти. Однако, если бы входные аргументы были более сложными, чем простые ints, я вижу, что их было бы сложнее оптимизировать (правила для временных аргументов применялись бы для C++).const, но компилятор видит, что sum нигде в функции не изменяется (при условии, что последующие функции не берут на нее изменяемую ссылку), поэтому он может просто сохранить значение в регистре, как и раньше. Однако это менее вероятно, чем в случае с функцией 4.a больше нигде в функции не используется (аналогично sum в функции 3), но Я бы не стал этого гарантировать. Это может потребовать изменения памяти стека в зависимости от того, как передаются аргументы функции (я не слишком хорошо знаком с тонкостями работы вызовов функций на этом уровне детализации).Верны ли мои предположения? Есть ли еще факторы, которые следует учитывать?
Обновлено: Пара пояснений в ответ на комментарии:
int, я ожидаю, что они будут использовать идентичные эвристики.Обычно недооценивают, насколько умными могут быть компиляторы => godbolt.org/z/n4b56v Это всегда один и тот же код, поэтому используйте то, что более читабельно.
Это не имеет большого значения; см. en.wikipedia.org/wiki/Static_single_assignment_form, что делают gcc и clang.





Хорошие современные компиляторы обычно не «заботятся» об именах, которые вы используете для хранения значений. Они выполняют пожизненный анализ значений и генерируют код на его основе. Например, учитывая:
int x = complicated expression 0;
... code using x
x = complicated expression 1;
... code using x
компилятор увидит, что complicated expression 0 используется в первом разделе кода, а complicated expression 1 используется во втором разделе кода, а имя x не имеет значения. Результат будет таким же, как если бы код использовал разные имена:
int x0 = complicated expression 0;
... code using x0
int x1 = complicated expression 1;
... code using x1
Таким образом, нет смысла повторно использовать переменную для другой цели; это не поможет компилятору сэкономить память или иным образом оптимизировать.
Даже если код был в цикле, например:
int x;
while (some condition)
{
x = complicated expression;
... code using x
}
компилятор увидит, что complicated expression рождается в начале тела цикла и заканчивается в конце тела цикла.
Это означает, что вам не нужно беспокоиться о том, что компилятор сделает с кодом. Вместо этого ваши решения должны основываться в основном на том, что понятнее написать и с большей вероятностью избежать ошибок:
a += b;, и использовать a позже в коде, как если бы он все еще содержал исходный параметр.int sum = a + b; в порядке; оно выражает намерение и делает его более понятным для читателей, когда одно и то же выражение используется в нескольких местах.
«Правильны ли мои предположения?» Простой способ это выяснить: скомпилируйте с помощью
-O3и посмотрите на вывод сборки каждой функции.