繁体   English   中英

按值传递与按引用或按指针传递的性能成本?

[英]Performance cost of passing by value vs. by reference or by pointer?

让我们考虑一个对象foo (它可能是一个int 、一个double 、一个自定义struct 、一个class ,等等)。 我的理解是,经过foo参考函数(或只是一个指针传递给foo )带来更高的性能,因为我们避免做出一个本地副本(这可能是昂贵的,如果foo大)。

但是,从这里的答案看来,实际上可以预期 64 位系统上的指针的大小为 8 个字节,而不管指向的是什么。 在我的系统上, float是 4 个字节。 这是否意味着,如果foo类型为float ,那么它是更有效的只是传递foo的值,而不是给它的指针(假定没有其他限制,这将使使用一个比函数内部的其他更有效)?

这取决于您所说的“成本”是什么意思,以及主机系统(硬件、操作系统)在操作方面的属性。

如果您的成本度量是内存使用,那么成本的计算是显而易见的 - 将复制的大小相加。

如果您的衡量标准是执行速度(或“效率”),那么游戏就不同了。 硬件(以及操作系统和编译器)倾向于通过专用电路(机器寄存器及其使用方式)针对复制特定大小的事物的操作性能进行优化。

例如,机器具有导致“最佳位置”的架构(机器寄存器、内存架构等)是很常见的 - 复制某种大小的变量最“有效”,但复制更大或更小的变量是不那么。 较大的变量复制成本更高,因为可能需要对较小的块进行多次复制。 较小的也可能花费更多,因为编译器需要将较小的值复制到较大的变量(或寄存器)中,对其进行操作,然后将值复制回来。

浮点的例子包括一些 cray 超级计算机,它们本机支持双精度浮点(在 C++ 中也称为double ),并且在单精度(在 C++ 中也称为float )上的所有操作都在软件中模拟。 一些较旧的 32 位 x86 CPU 也在内部使用 32 位整数,并且由于与 32 位之间的转换,对 16 位整数的操作需要更多的时钟周期(对于更现代的 32 位或 64 位,情况并非如此)位 x86 处理器,因为它们允许将 16 位整数复制到 32 位寄存器或从 32 位寄存器复制,并对其进行操作,这样的惩罚较少)。

按值复制一个非常大的结构比创建和复制它的地址效率低,这有点显而易见。 但是,由于上述因素,“最好按值复制该大小的内容”和“最好传递其地址”之间的交叉点不太清楚。

指针和引用往往以类似的方式实现(例如,通过引用传递可以以与传递指针相同的方式实现),但这并不能保证。

唯一确定的方法是测量它。 并意识到测量值会因系统而异。

有一件事没有人提到。

有一种称为 IPA SRA 的 GCC 优化,它自动将“通过引用”替换为“通过值”: https ://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html (-fipa-sra)

这很可能是为标量类型(例如 int、double 等)完成的,它没有非默认复制语义并且可以放入 cpu 寄存器。

这使得

void(const int &f)

可能一样快(和空间优化)

void(int f)

因此,启用此优化后,对小类型使用引用应该与按值传递它们一样快。

另一方面,由于涉及自定义复制语义,因此无法将按值传递(例如)std::string 优化为按引用速度。

据我了解,对所有内容使用按引用传递永远不会比手动选择按值传递的内容和按引用传递的内容慢。

这对于模板特别有用:

template<class T>
void f(const T&)
{
    // Something
}

总是最优的

您必须测试性能绝对至关重要的任何给定场景,但在尝试强制编译器以特定方式生成代码时要非常小心。

允许编译器的优化器以它选择的任何方式重写您的代码,只要最终结果可证明是相同的,这可以导致一些非常好的优化。

考虑到按值传递浮点数需要制作浮点数的副本,但在正确的条件下,通过引用传递浮点数可以允许将原始浮点数存储在 CPU 浮点寄存器中,并将该寄存器视为“引用”参数到函数。 相比之下,如果传递一个副本,编译器为了保存寄存器的内容,就得找个地方存放副本,甚至更糟的是,它可能因为需要一个寄存器而根本无法使用保留原始值(在递归函数中尤其如此!)。

如果您将引用传递给可以内联的函数,这种差异也很重要,其中引用可能会降低内联的成本,因为编译器不必保证复制的参数不能修改原始参数。

一种语言越是让你专注于描述你想要完成的事情而不是你想要如何完成,编译器就越能够找到创造性的方法来为你完成艰苦的工作。 特别是在 C++ 中,通常最好不要担心性能,而是专注于尽可能清楚和简单地描述你想要的东西。 通过尝试描述您希望如何完成工作,您将经常阻止编译器为您优化代码。

这是否意味着如果 foo 是 float 类型,那么按值传递 foo 会更有效?

按值传递浮点数可能更有效。 我希望它更有效 - 部分原因是你所说的:浮点数比你描述的系统上的指针小。 但除此之外,在复制指针时,仍然需要对指针进行解引用,以获取函数内的值。 指针添加的间接性可能会对性能产生重大影响。

效率差异可以忽略不计。 特别是,如果可以内联函数并启用优化,则可能不会有任何区别。

您可以通过测量找出在您的情况下按值传递浮点数是否有任何性能提升。 您可以使用分析工具来衡量效率。

您可以用参考替换指针,答案仍然同样适用。

使用引用是否存在某种开销,即必须取消引用指针的方式?

是的。 引用很可能与指针具有完全相同的性能特征。 如果可以使用引用或指针编写语义等效的程序,则两者都可能生成相同的程序集。


如果通过指针传递一个小对象比复制它快,那么对于相同大小的对象肯定是这样,你不同意吗? 指向指针的指针怎么样,大约是指针的大小,对吧? (大小完全相同。)哦,但指针也是对象。 因此,如果通过指针传递对象(例如指针)比复制对象(指针)快,那么将指向指针的指针传递给指向指针的指针......传递给指针的速度将比程序快使用更少的指针仍然比不使用指针的指针更快......也许我们在这里找到了无限的效率来源:)

如果您想要优化执行时间以避免随机访问,请始终优先考虑通过引用而不是指针。 对于按引用传递与按值传递,GCC 优化您的代码,以便不需要更改的小变量将按值传递。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM