c++ 按值传递与按引用或指针传递的性能代价?

xqk2d5yq  于 2023-02-17  发布在  其他
关注(0)|答案(6)|浏览(157)

让我们考虑一个对象foo(其可以是intdouble、定制的structclass我的理解是通过引用传递foo给一个函数(或者只是传递一个指向foo的指针)会带来更高的性能,因为我们避免了进行本地拷贝(如果foo很大,那么这可能会很昂贵)。
然而,从here的答案来看,不管指向的是什么,64位系统上的指针实际上都是8字节,在我的系统上,float是4字节,这是否意味着如果foofloat类型,那么仅仅通过值传递foo而不是给予它一个指针是更有效的(假设没有其他约束使得在函数内部使用一个比使用另一个更有效)?

pqwbnv8z

pqwbnv8z1#

这取决于您所指的“成本”,以及主机系统(硬件、操作系统)相对于操作的属性。
如果您的成本度量是内存使用量,那么成本的计算是显而易见的--将正在复制的所有内容的大小相加。
如果你的衡量标准是执行速度(或“效率”),那么游戏就不同了。硬件(以及操作系统和编译器)往往通过专用电路(机器寄存器及其使用方式)来优化复制特定大小的东西的操作性能。
例如,一台机器通常具有一种体系结构(机器寄存器、内存架构等),这导致了一个“甜蜜点”-复制某种大小的变量是最“有效”的,但复制较大或较小的变量就不那么有效了。较大的变量将花费更多的成本来复制,因为可能需要对较小的块进行多次复制。较小的也可能花费更多,因为编译器需要将较小的值复制到较大的变量(或寄存器)中,对其进行操作,然后将值复制回来。
浮点的例子包括一些cray超级计算机,它们本身就支持双精度浮点(在C中又称为double),以及所有单精度运算(在C中又名float)在软件中模拟。一些较老的32位x86 CPU也在内部使用32位整数,而16位整数上的操作由于转换到32位/从32位转换而需要更多的时钟周期(对于更现代的32位或64位x86处理器来说,这不是真的,因为它们允许将16位整数复制到32位寄存器/从32位寄存器复制16位整数,并对它们进行操作,而这种损失较少)。
通过值复制一个非常大的结构比创建和复制它的地址效率要低,这是一个显而易见的事实,但是,由于上述因素,“最好通过值复制这种大小的东西”和“最好传递它的地址”之间的交叉点就不那么清楚了。
指针和引用往往以类似的方式实现(例如,通过引用传递可以以传递指针的相同方式实现),但这并不保证。
唯一确定的方法是测量它,并认识到测量结果会因系统而异。

qnzebej0

qnzebej02#

有一件事没人提起。
有一种叫做IPA SRA的GCC优化,它可以自动将"按引用传递"替换为"按值传递":https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html(-国际食品药品监督管理局-新加坡药品监督管理局)
这很可能是针对标量类型(例如int、double等),它们没有非默认复制语义,可以装入cpu寄存器。
这使得

void(const int &f)

速度可能一样快(而且空间优化)

void(int f)

因此,启用了这种优化后,对小类型使用引用应该与通过值传递引用一样快。
另一方面,通过值传递(例如)std::string不能优化到通过引用传递的速度,因为涉及到自定义复制语义。
据我所知,对所有事情都使用引用传递应该永远不会比手动选择什么通过值传递,什么通过引用传递慢。
这对于模板尤其有用:

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

总是最优的

eit6fx6z

eit6fx6z3#

您必须测试性能绝对关键的任何给定场景,但是在尝试强制编译器以特定方式生成代码时要非常小心。
编译器的优化器被允许以它选择的任何方式重写代码,只要最终结果是可证明的相同的,这可以导致一些非常好的优化。
考虑到按值传递浮点数需要复制一个浮点数,但在适当的条件下,按引用传递浮点数可以允许将原始浮点数存储在CPU浮点寄存器中,并将该寄存器视为函数的“引用”参数。相比之下,如果传递一个副本,编译器必须找到一个位置来存储副本,以便保留寄存器的内容。甚至更糟的是,它可能根本不能使用寄存器,因为需要保留原始的寄存器(在递归函数中尤其如此!2)。
如果要将引用传递给一个可以内联的函数,这种差异也很重要,因为编译器不必保证复制的参数不能修改原始参数,所以引用可以降低内联的成本。
一种语言越是允许你专注于描述你想要做什么而不是你想要如何做,编译器就越能找到创造性的方法来为你完成这项艰巨的工作。特别是在C++中,通常最好不要担心性能,而是专注于尽可能清楚和简单地描述你想要做什么。通过试图描述你想要如何做这项工作,您将经常阻止编译器为您完成优化代码的工作。

l0oc07j2

l0oc07j24#

这是否意味着,如果foo是float类型,那么直接按值传递foo会更高效?
按值传递浮点数可能会更高效。我希望它会更高效--部分原因是你说的:在你所描述的系统中,浮点数比指针小。但是,当你复制指针时,你仍然需要解引用指针来获取函数中的值。指针增加的间接性会对性能产生很大的影响。
效率上的差异可以忽略不计,特别是,如果函数可以内联,并且启用了优化,则可能不会有任何差异。
您可以通过度量来了解在您的情况下按值传递浮点数是否会带来性能增益,也可以使用分析工具来度量效率。
你可以用参考代替指点,答案同样适用。
在使用引用时是否有某种开销,就像指针必须被解引用时那样?
是的。引用很可能和指针具有完全相同的性能特征。如果可以用引用或指针编写一个语义上等价的程序,那么两者很可能会生成相同的程序集。
如果用指针传递一个小对象比复制它快,那么对于同样大小的对象也是如此,你不同意吗?,用指针传递一个指针怎么样,这和指针大小差不多,对吗?(大小完全一样。)哦,但是指针也是对象。所以,如果传递一个对象(如指针)比复制对象快(指针),那么传递一个指针到一个指针,到一个指针,到一个指针,会比指针少的程序快,比不使用指针的程序快。也许我们在这里找到了无限的效率来源:)

7bsow1i6

7bsow1i65#

如果你想优化执行时间以避免随机访问,那么一定要优先考虑引用传递而不是指针传递。对于引用传递和值传递,GCC会优化你的代码,这样不需要改变的小变量就会通过值传递。

mrzz3bfm

mrzz3bfm6#

不敢相信还没有人提出正确的答案。
在64位系统上,传递8字节或4字节的成本完全相同,原因是数据总线是64位宽(即8字节),因此即使您只传递4字节-对机器也没有影响:数据总线为8字节宽。
只有当你想移动超过64位的数据时,成本才会增加。所有等于或低于64位的数据都需要相同数量的时钟周期。

相关问题