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

时间:2016-10-21 21:26:58

标签: c++ pointers pass-by-reference pass-by-value pass-by-pointer

让我们考虑一个对象foo(可能是intdouble,自定义structclass,等等)。我的理解是,通过引用foo来传递一个函数(或只是将指针传递给foo)会导致更高的性能,因为我们避免制作本地副本(如果foo这可能会很昂贵很大)。

然而,从答案here来看,64位系统上的指针在实践中可以预期大小为8字节,而不管指向的是什么。在我的系统上,float是4个字节。这是否意味着如果foo的类型为float,则更高效只能通过值传递foo而不是指向它(假设没有其他约束可以使得在函数内使用一个比另一个更高效的那些?)

5 个答案:

答案 0 :(得分:15)

这取决于“成本”的含义,以及主机系统(硬件,操作系统)在操作方面的属性。

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

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

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

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

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

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

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

答案 1 :(得分:7)

有一件事没有人提到过。

有一种称为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 by value无法优化为引用速度,因为涉及自定义复制语义。

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

这对模板非常有用:

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

始终是最佳的

答案 2 :(得分:4)

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

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

考虑按值传递float需要复制float,但在正确的条件下,通过引用传递float可以允许将原始float存储在CPU浮点寄存器中,并将该寄存器视为& #34;参考&#34;函数的参数。相比之下,如果您传递副本,编译器必须找到存储副本的位置以保留寄存器的内容,或者更糟糕的是,它可能根本无法使用寄存器,因为需要保留原始(在递归函数中尤其如此!)。

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

语言越多,您就可以专注于描述您想要完成的工作,而不是您希望如何完成,编译器能够找到创造性的方法为您做出艰苦的工作。特别是在C ++中,通常最好不要担心性能,而是专注于尽可能清晰简单地描述您想要的内容。通过尝试描述您希望如何完成工作,您将经常阻止编译器为您优化代码。

答案 3 :(得分:3)

  

这是否意味着如果foo的类型为float,那么通过值传递foo会更有效吗?

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

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

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

您可以用引用替换指针,答案仍然适用。

  

使用引用是否存在某种开销,指针必须解除引用时的方式?

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

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

答案 4 :(得分:0)

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