在C++函数中重新分配变量是否会增加执行函数的计算成本



考虑以下函数:

void doMath(const std::vector<double> &v)
{
double a = v[0];
double b = v[1];
double c = v[2];
double d = v[3];
double e = v[4];
double f = v[5];
//do math
}

如果我有复杂的方程式,通常,更改变量的名称会使键入方程式变得更加容易(例如,避免拼写错误,更快的打字,更具可读性)。

  • 我知道计算成本很小,但是与在操作中使用 v[0] 而不是 a = v[0] 相比,是否有任何额外的计算成本?例如,编译器是否知道赋值不是必需的,并在编译期间对其进行优化?
  • 数据类型有区别吗?我想对于更复杂的数据类型,是的(对吗?例如,如果输入是std::vector<std::vector<double>>,并且我创建std::vector<double>并将向量向量的每个元素重新分配给单独的向量,编译器会优化这一点吗?

不,不是一般的。 它可以更改函数的行为。

double a = v[0];
double b = v[1];
double c = v[2];
double d = v[3];
double e = v[4];
double f = v[5];

af中的每一个都是创建副本时v[i]状态的副本。 不必创建此副本,但编译器必须保证,如果v[i]在此点之后更改,则该更改不会反映在通过变量a的状态中f

C++编译器使用"静态单一赋值"等技术在优化代码之前对其进行分解。 我的意思是,如果你这样做:

double a = v[0];
a = a + v[1];

编译器很可能有两个a名称

double a' = v[0];
double a'' = a' + v[1];

在这里,每个命名值仅分配给一次。 然后,它会解开您的代码以使用局部变量的这个"不可变"版本。

这不适用于通过引用或指针传递的值。 在那里,编译器必须假设每次都遵循引用/指针,或者必须证明间接寻址在两个相邻行处产生完全相同的值。

double a = v[0];
double b = v[0];

编译器很可能能够证明ab具有相同的值。 因此,它可以跳过从引用v和从其内部指针到缓冲区的读取两次。

double a = v[0];
some_function_pointer();
double b = v[0];

在这种情况下,编译器无法证明ab是相同的值,除非它计算出some_function_pointer函数的作用。 缺乏这种能力,它必须独立计算ab

当你这样做时

double a = v[0];
double b = v[1];
double c = v[2];
double d = v[3];
double e = v[4];
double f = v[5];

您可以"锁定"正在读取的矢量的值,拍摄单个快照。 因此,之后使用af的代码可能会生成与使用v[0]v[5]的代码不同的汇编,只是因为编译器更容易证明向量或其内容没有被修改。

double const& a = v[0];
double const& b = v[1];
double const& c = v[2];
double const& d = v[3];
double const& e = v[4];
double const& f = v[5];

这最终更接近于在使用点使用v[0]。 在这里,对向量缓冲区的更改应该反映在af中,但向量本身的变化不应该是(!)。

当你通过const&传递向量时,这只是承诺你不会在没有const_cast的情况下修改v,它不保证在你运行时不会修改向量。 任何引用了不const向量的人在运行时都可以这样做(例如,你调用回调、系统函数或编译器无法内联的任何内容)都可以自由修改向量或其缓冲区。


对于更复杂的数据类型,除非副本的修改方式与源不同,否则double的副本是无用的。

vector<double>的副本在实践中并不算什么。vector<double>的副本请求分配新缓冲区。

编译器可以自由地注意到副本和源代码都仅以不可变的方式使用并消除它,但消除比消除单纯的double要困难得多。 如果确实发生,副本会更昂贵。

每次添加双精度时都会完成double的副本,以便将其放入寄存器中。 因此,单个"不必要的"副本并不是真正的成本;此副本可以与您将其添加到另一个数字时所做的副本相同,因为编译器非常擅长消除此类重复项。

当您对vector执行大多数操作时,不会完成该的副本。 因此,不必要的副本可能会对性能产生显著影响。


消除额外的doubles是一个"仿佛"优化的问题。 额外的双精度实际上存在于C++指定行为的抽象机器中。

但是双打对他们的存在没有太多的副作用。 由于C++指定了抽象机器的行为,并且只需要它运行的具体机器代码与之一致,因此删除抽象机器中存在的东西是完全合法的! 只要它的行为就好像它们存在一样(直到未定义的行为)。

临时向量也是如此。 除了向量在它们的存在中具有复杂的副作用,因为它们会做一些事情,比如分配内存。 现代C++编译器可以自由地消除不必要的内存分配,但这比消除仅 64 位平面值更难。

嗯,不,但有时是的

一方面,赋值变量可能是一个昂贵的操作(我们可以提到在不需要时复制 std::string),另一方面,你想要一个漂亮的、可读的代码。事实是您的问题没有正确答案。很大程度上取决于具体任务。在某些情况下,优化器可能会考虑优化冗余复制,在某些冗余复制优化后会存在。有人说在这种情况下您可以使用引用,但也没有人(或纠正我)不保证它们不会执行不必要的操作。

所以在某些情况下不会有区别,但在编写代码之前你无法确定。

我能给你的主要建议是每次优化后测量代码的执行时间,并记住过早优化是万恶之源

最新更新