提问人:space_voyager 提问时间:10/22/2016 最后编辑:Communityspace_voyager 更新时间:2/17/2023 访问量:57421
按值传递与按引用或指针传递的性能成本?
Performance cost of passing by value vs. by reference or by pointer?
问:
让我们考虑一个对象(可以是 , a , a custom , a , whatever)。我的理解是,通过引用函数(或只是传递指针)传递会导致更高的性能,因为我们避免了制作本地副本(如果很大,这可能会很昂贵)。foo
int
double
struct
class
foo
foo
foo
但是,从此处的答案来看,无论指向什么,实际上 64 位系统上的指针的大小似乎都为 8 字节。在我的系统上,a 是 4 个字节。这是否意味着如果 是 类型,那么只传递值而不是给它一个指针更有效(假设没有其他约束会使在函数中使用一个比另一个更有效)?float
foo
float
foo
答:
这是否意味着如果 foo 是 float 类型,那么仅按值传递 foo 会更有效?
按值传递浮点数可能更有效。我希望它更有效率 - 部分原因是你说的:浮点数比你描述的系统上的指针小。但除此之外,当您复制指针时,您仍然需要取消引用指针以获取函数中的值。指针添加的间接内容可能会对性能产生重大影响。
效率差异可以忽略不计。特别是,如果函数可以内联并启用优化,则可能不会有任何区别。
您可以通过测量来了解在您的案例中按值传递浮点数是否有任何性能提升。您可以使用分析工具来衡量效率。
您可以用引用替换指针,答案仍然同样适用。
使用引用时是否存在某种开销,就像必须取消引用指针时一样?
是的。引用可能具有与指针完全相同的性能特征。如果可以使用引用或指针编写语义等效的程序,则两者都可能会生成相同的程序集。
如果通过指针传递一个小对象比复制它更快,那么对于相同大小的对象来说肯定是正确的,你不同意吗?指针到指针怎么样,这大约是指针的大小,对吧?(大小完全相同。哦,但指针也是对象。因此,如果通过指针传递对象(例如指针)比复制对象(指针)更快,那么将指针传递给指针的指针到指针...指针会比指针较少的 progarm 更快,但仍然比不使用指针的指针更快......也许我们在这里找到了无限的效率源泉:)
评论
这取决于您所说的“成本”的含义,以及主机系统(硬件、操作系统)与操作相关的属性。
如果您的成本度量是内存使用量,那么成本的计算是显而易见的 - 将正在复制的任何内容的大小相加。
如果你的衡量标准是执行速度(或“效率”),那么游戏就不同了。硬件(以及操作系统和编译器)往往通过专用电路(机器寄存器及其使用方式)来优化复制特定大小事物的操作性能。
例如,对于一台机器来说,通常有一个架构(机器寄存器、内存架构等),这会导致一个“甜蜜点”——复制一定大小的变量是最“有效”的,但复制更大或更小的变量就不那么“有效”。较大的变量复制成本更高,因为可能需要对较小的块进行多个复制。较小的值也可能花费更多,因为编译器需要将较小的值复制到较大的变量(或寄存器)中,对其执行操作,然后将值复制回来。
浮点的例子包括一些 cray 超级计算机,它们本身支持双精度浮点(在 C++ 中又名),并且所有单精度(在 C++ 中)的操作都在软件中模拟。一些较旧的 32 位 x86 CPU 也在内部使用 32 位整数工作,并且由于与 32 位之间的转换,对 16 位整数的操作需要更多的时钟周期(对于更现代的 32 位或 64 位 x86 处理器,情况并非如此,因为它们允许将 16 位整数复制到 32 位寄存器或从 32 位寄存器复制 16 位整数, 并对它们进行手术,减少此类处罚)。double
float
按值复制一个非常大的结构比创建和复制其地址效率低,这有点不费吹灰之力。但是,由于上述因素,“最好按值复制这种大小的东西”和“最好传递其地址”之间的交叉点不太清楚。
指针和引用往往以类似的方式实现(例如,按引用传递可以以与传递指针相同的方式实现),但这并不能保证。
唯一确定的方法是测量它。并意识到测量值会因系统而异。
评论
您必须测试性能绝对关键的任何给定方案,但在尝试强制编译器以特定方式生成代码时要非常小心。
编译器的优化器可以按照它选择的任何方式重写你的代码,只要最终结果是可证明的,这可能会导致一些非常好的优化。
考虑按值传递浮点数需要复制浮点数,但在正确的条件下,通过引用传递浮点数可以允许将原始浮点数存储在 CPU 浮点寄存器中,并将该寄存器视为函数的“引用”参数。相反,如果你传递一个副本,编译器必须找到一个存储副本的地方,以保留寄存器的内容,或者更糟糕的是,它可能根本无法使用寄存器,因为需要保留原始副本(这在递归函数中尤其如此!
如果要将引用传递给可以内联的函数,则此差异也很重要,其中引用可能会降低内联的成本,因为编译器不必保证复制的参数无法修改原始参数。
一种语言越能让你专注于描述你想做什么,而不是你想如何做,编译器就越能找到创造性的方法来为你做艰苦的工作。特别是在C++中,通常最好不要担心性能,而是专注于尽可能清晰简单地描述你想要的东西。通过尝试描述您希望如何完成工作,您通常会阻止编译器为您优化代码。
评论
有一件事没有人提到。
有一种称为 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
}
始终是最佳的
评论
template<typename T> void f(T&&) { ... }
如果您希望优化执行时间以避免随机访问,请始终优先考虑按引用传递而不是指针传递。对于按引用传递与按值传递,GCC 会优化您的代码,以便将不需要更改的小变量按值传递。
评论
不敢相信还没有人提出正确的答案。
在 64 位系统上,传递 8 个字节或 4 个字节的成本完全相同。这样做的原因是数据总线是 64 位宽(即 8 个字节),因此即使您只传递 4 个字节 - 它对机器也没有影响:数据总线是 8 个字节宽。
只有当您要移动超过 64 位时,成本才会增加。所有等于或低于 64 位的时钟周期数相同。
上一个:传递对象时数据丢失
评论