通过值时的经验法则比通过const引用更快吗?

时间:2014-10-15 16:32:14

标签: c++ c++11 pass-by-reference pass-by-value

假设我有一个带有T类型参数的函数。 它不会改变它,所以我可以选择通过const引用const T&或值T传递它:

void foo(T t){ ... }
void foo(const T& t){ ... }

在通过const引用变得比通过值更便宜之前,是否有一个经验法则是T应该变大?例如,假设我知道sizeof(T) == 24。我应该使用const引用还是值?

我认为T的复制构造函数是微不足道的。否则,问题的答案取决于复制构造函数的复杂性,当然。

我已经找过类似的问题并偶然发现了这个问题:

template pass by value or const reference or...?

然而,接受的答案( https://stackoverflow.com/a/4876937/1408611)没有说明任何细节,它只是声明:

  

如果您希望T始终是数字类型或非常类型   便宜的复制,然后你可以按价值来论证。

所以它并没有解决我的问题,而是改写它:一个类型必须有多小才能复制"非常便宜?

7 个答案:

答案 0 :(得分:27)

如果您有理由怀疑有一个值得获得的性能提升,请使用经验法则和度量进行删除。您引用的建议的目的是您不会无缘无故地复制大量数据,但也不要通过将所有内容都作为引用来危害优化。如果某些东西处于“明显便宜复制”和“复制成本高昂”之间的边缘,那么你可以买得起任何一种选择。如果您必须将决定从您手中夺走,请翻转硬币。

如果类型没有时髦的复制构造函数且其sizeof很小,则复制成本很低。 “小”没有硬数,这是最优的,甚至不是基于每个平台,因为它非常依赖于调用代码和函数本身。走你的直觉吧。一,二,三个字很小。十,谁知道。 4x4矩阵不小。

答案 1 :(得分:13)

传递值而不是const引用的优点是编译器知道该值不会发生变化。 " const int& X"并不意味着价值不能改变;它只表示您的代码不允许通过使用标识符x来更改它(没有编译器会注意到的某些强制转换)。采取这个可怕但完全合法的例子:

static int someValue;

void g (int i)
{
    --someValue;
}

void f (const int& x)
{
    for (int i = 0; i < x; ++i)
        g (i);
}

int main (void)
{
    someValue = 100;
    f (someValue);
    return 0;
}

内部函数f,x实际上不是常数!每次调用g(i)时它都会改变,所以循环只从0到49运行!由于编译器通常不会知道是否编写了这样的可怕代码,因此必须假设在调用g时x 可能会更改。因此,您可以期望代码比使用&#34; int x&#34;慢。

对于许多可能通过引用传递的对象来说,显然也是如此。例如,如果您通过const&amp;传递对象,并且该对象具有int或unsigned int的成员,那么使用char *,int *或unsigned int * 的任何赋值可能会更改成员,除非编译器能够证明不是这样。通过值传递,证明对编译器来说更容易。

答案 2 :(得分:12)

在我看来,最合适的经验法则是:

sizeof(T) >= sizeof(T*)

这背后的想法是,当您通过引用,最坏时,您的编译器可能会使用指针来实现它。

这当然没有考虑复制构造函数的复杂性和移动语义以及围绕对象生命周期创建的所有地狱。

此外,如果您不关心微优化,您可以通过const引用传递所有内容,在大多数机器上指针是4或8个字节,很少有类型小于那个,甚至在这种情况下你会丢失一些(少于超过8)字节复制操作和一些在现代世界中很可能不会成为你瓶颈的间接因素:)

答案 3 :(得分:4)

我相信我会尽可能选择传递值(即:当语义规定我不需要实际对象时)。我相信编译器可以执行适当的移动和复制省略。

在我的代码语义正确之后,我会对其进行分析以查看我是否正在制作任何不必要的副本;我会相应地修改它们。

我相信这种方法可以帮助我专注于我软件中最重要的部分:正确性。而且我不会受到编译器的影响---干扰;禁止---进行优化(我知道我无法击败它)。

话虽如此,名义上的引用被实现为指针。因此,在真空中,不考虑语义,复制精度,移动语义和类似的东西,通过指针/引用传递大小大于指针的任何东西会更“有效”。

答案 4 :(得分:3)

摘要&#34; T&#34;摘要&#34; C ++&#34;经验法则是使用更好地反映意图的方式,对于没有被修改的论证几乎总是通过值&#34;。此外,具体的真实世界编译器期望这样一个抽象的描述,并以最有效的方式传递你的T,无论你在源代码中如何做到这一点。

或者,谈论naivie编译和作文,&#34;非常便宜复制&#34;是&#34;你可以在一个寄存器中加载任何东西&#34;。没有比这更便宜的东西。

答案 5 :(得分:3)

如果您要使用&#34;经验法则&#34;对于by-value与by-const-reference,请执行以下操作:

  • 选择一种方法并随处使用
  • 同意所有同事中的哪一个
  • 仅在稍后,在&#34;手动调整表现&#34;阶段,开始改变事情
  • 然后,只有在看到可衡量的改进
  • 时才更改它们

答案 6 :(得分:1)

这里的人是正确的,大多数时候,当结构/类的大小很小并且没有花哨的复制构造函数时,它并不重要。然而,这不是无知的借口。这里有what happens在一些现代ABI中,如x64。您可以看到,在该平台上,经验法则的一个很好的阈值是传递值,其中类型是POD类型并且sizeof()&lt; = 16,因为它将在两个寄存器中传递。经验法则是好的,它们可以避免浪费时间和有限的智力来做出这样的小动作,而这些决定不会动摇针。

然而,有时它会很重要。如果您已经通过剖析器确定您拥有其中一个案例(除非它非常明显,否则之前没有!),那么您需要了解低级细节 - 不要听到关于它如何做的令人安慰的陈词滥调#39;重要的是,SO充满了。要记住的一些事情:

  • 按值传递告诉编译器该对象没有改变。有一些邪恶的代码,涉及线程或全局变量,其中const引用指向的东西正在其他地方被更改。虽然你肯定不会编写恶意代码,但编译器可能很难证明这一点,因此必须生成非常保守的代码。
  • 如果有太多的参数要通过寄存器传递,那么大小是什么并不重要,该对象将在堆栈​​上传递。
  • 今天小而POD的对象明天可能会增长并获得昂贵的复制构造函数。添加这些东西的人可能没有检查它是通过引用还是通过值传递,所以过去表现很好的代码可能突然突然出现。如果您在没有全面的性能回归测试的团队中工作,那么通过const引用传递是一种更安全的选择。迟早会被你咬伤。