C/C++编译器优化:我应该更喜欢创建新的变量,重新使用现有的变量,还是完全避免变量



这是我一直想知道的问题:编译器在重新使用现有变量、创建新的(理想情况下为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);
}

我的直觉是:

  • 在这种特殊情况下,函数4最容易优化,因为它明确说明了使用数据的意图。它告诉编译器:;我们将对两个输入参数求和,其结果不会被修改,并且我们将以相同的方式将结果传递给后续的每个函数调用"我预计sum变量的值只会被放入一个寄存器中,不会发生实际的底层内存访问
  • 函数1是下一个最容易优化的函数,尽管它需要编译器进行更多的推理。编译器必须发现a + b以相同的方式用于每个函数调用,并且必须知道每次使用该表达式时a + b的结果都是相同的。我仍然希望a + b的结果被放入寄存器中,而不是提交到内存中。然而,如果输入参数比普通的ints更复杂,我可以看到这更难优化(临时规则适用于C++)
  • 函数3是接下来最容易的:结果不放入const变量中,但编译器可以看到sum在函数中的任何地方都没有被修改(假设后续函数没有对其进行可变引用),因此它可以像以前一样将值存储在寄存器中。不过,与函数4的情况相比,这种可能性较小
  • 函数4对优化的帮助最小,因为它直接修改了传入的函数参数。我不能100%确定编译器在这里会做什么:我不认为期望它足够智能,能够发现函数中的其他地方没有使用a是不合理的(类似于函数3中的sum),但我不能保证。这可能需要根据函数参数的传入方式修改堆栈内存(我不太熟悉函数调用在该详细级别上的工作方式)

我的假设正确吗?还有更多的因素需要考虑吗?

编辑:回应评论的几个澄清:

  • 如果C和C++编译器以不同的方式处理上面的例子,我很想知道为什么。我可以理解,C++会根据对任何可能输入到这些函数的对象的约束来进行不同的优化,但对于像int这样的基元类型,我希望它们使用相同的启发式方法
  • 是的,我可以用优化编译并查看汇编输出,但我不知道汇编,所以我在这里问

优秀的现代编译器通常不"关心"用于存储值的名称。他们执行价值的生命周期分析,并在此基础上生成代码。例如,给定:

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
}

编译器将看到CCD_ 15出生在循环体的开始处并且结束于循环体的结束处。

这意味着你不必担心编译器会对代码做什么。相反,你的决策应该主要以更清晰、更容易避免错误的内容为指导:

  • 避免将变量重复用于多个目的。例如,如果有人稍后更新您的函数以添加新功能,他们可能会错过您使用a += b;更改函数参数的事实,并在代码中稍后使用a,就好像它仍然包含原始参数一样
  • 请自由创建新的变量来保存重复的表达式。CCD_ 18良好;当同一表达在多个地方使用时,它表达了意图,并使读者更清楚
  • 限制变量(以及标识符)的范围。只在需要它们的最内部范围中声明它们,例如在循环内部而不是外部。避免在不再合适的地方意外使用变量

最新更新