40

让我们考虑一个对象foo(可能是a 、 inta double、 custom struct、 aclass等等)。我的理解是,通过foo引用传递函数(或仅传递指向 的指针foo)会导致更高的性能,因为我们避免制作本地副本(如果foo很大,这可能会很昂贵)。

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

4

5 回答 5

35

这取决于您所说的“成本”以及主机系统(硬件、操作系统)相对于操作的属性。

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

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

例如,对于一台机器来说,通常有一个架构(机器寄存器、内存架构等)会导致“最佳位置”——复制某种大小的变量是最“有效的”,但复制更大或更小的变量是少这样。较大的变量将花费更多的复制成本,因为可能需要对较小的块进行多次复制。较小的也可能成本更高,因为编译器需要将较小的值复制到较大的变量(或寄存器)中,对其进行操作,然后将值复制回来。

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

按值复制一个非常大的结构将比创建和复制其地址效率低,这有点不费吹灰之力。但是,由于上述因素,“最好按值复制该大小的东西”和“最好传递其地址”之间的交叉点不太清楚。

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

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

于 2016-10-21T21:56:30.850 回答
31

没有人提到一件事。

有一种称为 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
}

总是最优的

于 2018-03-27T22:07:33.237 回答
7

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

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

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

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

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

于 2016-10-21T22:07:12.450 回答
6

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

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

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

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

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

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

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


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

于 2016-10-21T21:38:58.537 回答
1

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

于 2021-06-24T03:01:48.700 回答