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

spa*_*ger 26 c++ pointers pass-by-reference pass-by-value pass-by-pointer

让我们考虑一个对象foo(可能是一个int,一个double,一个习惯struct,一个class,等等).我的理解是,通过foo引用函数(或只是传递指针foo)来传递更高的性能,因为我们避免制作本地副本(如果foo很大则可能很昂贵).

但是,从这里的答案来看,似乎64位系统上的指针实际上可以预期大小为8字节,无论指向什么.在我的系统上,a float是4个字节.这是否意味着如果foo是类型float,那么仅通过值传递而不是给它指向它是更有效的foo(假设没有其他约束可以使得使用一个比函数内的另一个更有效)?

Pet*_*ter 20

它取决于"成本"的含义,以及主机系统(硬件,操作系统)在操作方面的属性.

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

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

例如,通常情况下,机器具有导致"最佳点"的架构(机器寄存器,存储器架构等) - 复制某些大小的变量是最"有效"的,但复制较大的OR SMALLER变量是不那么.较大的变量复制成本会更高,因为可能需要对较小的块进行多个副本.较小的也可能花费更多,因为编译器需要将较小的值复制到较大的变量(或寄存器)中,对其执行操作,然后将值复制回来.

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

按值复制一个非常大的结构比创建和复制它的地址效率低,这有点简单.但是,由于上述因素,"最好按价值复制某种尺寸"和"最好通过其地址"之间的交叉点不太清楚.

指针和引用倾向于以类似的方式实现(例如,通过引用传递可以以与传递指针相同的方式实现)但是不能保证.

唯一可以确定的方法是衡量它.并意识到测量结果会因系统而异.

  • 您是否知道架构的实际示例,其中传递较小的类型(例如char)比传递更大的类型(如int或指针)更昂贵? (3认同)

pek*_*u33 11

没有人提到过一件事.

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

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

这使得

void(const int &f)
Run Code Online (Sandbox Code Playgroud)

可能同样快(并且空间优化)

void(int f)
Run Code Online (Sandbox Code Playgroud)

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

另一方面,传递(例如)std :: string by value无法优化为引用速度,因为涉及自定义复制语义.

根据我的理解,对所有内容使用pass by reference应该永远不会比手动选择要传递的值以及通过引用传递的内容慢.

这对于模板非常有用:

template<class T>
void f(const T&)
{
    // Something
}
Run Code Online (Sandbox Code Playgroud)

永远是最佳的


小智 7

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

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

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

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

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

  • 假设没有函数内联发生。然后通过引用方式传递 - 在我知道的调用约定上 - 指向原始内存位置 **HAS** 的指针要传递给函数,并且需要将该值作为指针实际存储在内存中不能指向寄存器。按值传递时,您可能必须将错误从一个寄存器复制到另一个寄存器(如果在函数调用后未使用该值,则不必),但不必将其存储在内存中。 (5认同)
  • 通常情况恰恰相反:当您通过引用/指针传递参数时,那么 - 实际上 - 该参数始终必须写入内存,而按值传递有时允许将数据保存在寄存器中。 (2认同)

eer*_*ika 6

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

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

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

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

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

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

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


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