我现在应该通过价值吗?

时间:2015-09-29 13:22:07

标签: c++ performance pass-by-reference

在这个talk中(对声音感到抱歉)Chandler Carruth建议不要在绝大多数情况下通过引用,甚至const引用,因为它限制了后端执行优化的方式。

他声称在大多数情况下副本可以忽略不计 - 我很高兴地相信,大多数数据结构/类等都在堆栈上分配了很小的部分 - 特别是与后端必须假设指针相比时别名以及可以对引用类型执行的所有讨厌的事情。

让我们说我们在堆栈上有大对象 - 比如~4kB和一个对这个对象的实例做某事的函数(假设是独立函数)。

经典我会写:

void DoSomething(ExpensiveType* inOut);
ExpensiveType data;
...
DoSomething(&data);

他建议:

ExpensiveType DoSomething(ExpensiveType in);
ExpensiveType data;
...
data = DoSomething(data);

根据我从谈话中得到的结果,第二种方法倾向于更好地进行优化。虽然我有多大这样的东西是有限制的,或者几乎在所有情况下后端拷贝缺省的东西都会更喜欢这些值?

编辑:为了澄清我对整个系统感兴趣,因为我觉得这对我编写代码的方式有重大改变,我已经使用了refs而不是钻进我的值任何大于整数类型的东西现在都很长。

EDIT2:我也测试了它,结果和代码here。真的没有竞争,因为我们已经教了很长时间,指针是一种快得多的做事方法。现在让我感到兴趣的是为什么在那次演讲中我们提出了我们的价值所在,但是由于这些数字并不支持它,所以我不会这样做。

2 个答案:

答案 0 :(得分:2)

我现在已经看过Chandler的部分演讲了。我认为现在的一般性讨论是“我现在应该总是通过价值”#34;不做他的谈话正义。编辑:实际上他之前已经讨论过他的演讲,value semantics vs output params with large data structures以及Eric Niebler的博客,http://ericniebler.com/2013/10/13/out-parameters-vs-move-semantics/

回到钱德勒。在关键注释中,他特别提到了(在其他地方提到的4x-5x分钟左右),提到了以下几点:

  • 如果优化器无法看到被调用函数的代码,则比传递ref或值有更大的问题。它几乎阻止了优化。 (此时有一个关于链接时间优化的后续问题,可能会在稍后讨论,我不知道。)
  • 他推荐新的经典"使用移动语义返回值的方法。而不是旧学校方式将对现有对象的引用作为输入输出参数传递,而是应该在本地构造并移出值。最大的优点是优化器可以确保对象的任何部分都没有被替换,因为只有该函数才能访问它。
  • 他提到线程,在全局变量中存储变量值,以及输出等可观察行为作为未知数的示例,当仅传递refs /指针时,这些未知数会阻止优化。我认为抽象描述可能是"本地代码不能假设本地值更改在其他地方未被检测到,并且它不能假设本地未更改的值根本没有改变"。通过本地副本,可以做出这些假设。

显然,当按值传递(并且可能,如果对象无法移动,返回时),在复制成本和优化优势之间存在折衷。大小和其他使复制成本高昂的东西会使参考策略达到平衡,而对函数中的对象进行大量优化工作会使其转向价值传递。 (他的例子包括指向整数的指针,而不是4k大小的对象。)

根据我观察的部分,我认为钱德勒并不认为钱德勒是一种一刀切的策略。我认为他主要是在传递out参数而不是返回一个新对象的情况下通过引用来传递。他的例子不是关于修改现有对象的函数。

总的来说:

程序应该表达程序员的意图。如果您需要副本,请务必复制!如果要修改现有对象,请务必使用引用或指针。只有当副作用或运行时行为变得无法忍受时;真的只有尝试做一些聪明的事情。

还应该意识到编译器优化有时会令人惊讶。其他平台,编译器,编译选项,类库甚至只是对您自己的代码进行小的更改都可能会阻止编译器拯救。在许多情况下,变更的运行时成本完全出乎意料。

答案 1 :(得分:0)

也许你把这部分话题脱离了背景或某事。对于大型对象,通常取决于函数是否需要对象的副本。例如:

ExpensiveType DoSomething(ExpensiveType in) 
{
    cout << in.member;
}

当你可以通过const引用传递时,你浪费了大量资源不必要地复制对象。

但如果功能是:

ExpensiveType DoSomething(ExpensiveType in) 
{
    in.member = 5;
    do_something_else(in);
}

我们不想修改调用函数的对象,那么这段代码可能比以下代码更有效:

ExpensiveType DoSomething(ExpensiveType const &inr) 
{
    ExpensiveType in = inr;
    in.member = 5;
    do_something_else(in);
}

当使用rvalue调用时会出现差异(例如DoSomething( ExpensiveType(6) );后者创建一个临时的,复制,然后销毁两者;而前者将创建一个临时的并使用它来移动构造{{1} (我认为这甚至可以进行复制)。

NB。不要将指针用作黑客来实现传递引用。 C ++通过引用原生传递。