【问题标题】:When will a C++11 compiler make RVO and NRVO outperform move semantics and const reference binding?C++11 编译器何时会使 RVO 和 NRVO 优于移动语义和 const 引用绑定?
【发布时间】:2011-09-25 18:41:18
【问题描述】:

考虑从函数返回启用了移动语义的“完整”对象的情况,如std::basic_string<>

std::wstring build_report() const
{
    std::wstring report;
    ...

    return report;
}

那么,我是否真的可以做出“最佳”选择,是否将返回的字符串与移动语义一起使用,如

const std::wstring report(std::move(build_report()));

或者如果我应该依靠 (N)RVO 来发生

const std::wstring report(build_report());

或什至将 const 引用绑定到临时用

const std::wstring& report(build_report());

有什么方案可以确定性地选择这些选项(如果有的话)?

编辑 1: 请注意,上面使用的 std::wstring 只是启用移动语义的类型的示例。它也可以换成你的arbitrary_large_structure。 :-)

编辑 2:我在 VS 2010 中运行速度优化的发布版本时检查了生成的程序集:

std::wstring build_report(const std::wstring& title, const std::wstring& content)
{
    std::wstring report;
    report.append(title);
    report.append(content);

    return report;
}

const std::wstring title1(L"title1");
const std::wstring content1(L"content1");

const std::wstring title2(L"title2");
const std::wstring content2(L"content2");

const std::wstring title3(L"title3");
const std::wstring content3(L"content3");

int _tmain(int argc, _TCHAR* argv[])
{
    const std::wstring  report1(std::move(build_report(title1, content1)));
    const std::wstring  report2(build_report(title2, content2));
    const std::wstring& report3(build_report(title3, content3));

    ...

    return 0;
}

两个最有趣的结果:

  • report1 显式调用std::move 以使用移动构造函数三倍指令计数。
  • 正如 James McNellis 在 his answer belowreport2report3 中所指出的,确实生成相同的程序集,其指令比显式调用 std::move 少 3 倍。

【问题讨论】:

  • 我认为对move 的调用没有被内联和消除,这真的很奇怪。
  • @James:是的,特别是考虑到 Kerrek SB 的评论,即 STL 说 RVO 发生在其他任何事情之前。 (stackoverflow.com/questions/6531700/…) 你不是在STL附近,所以你可以走过去问他吗? ;-)
  • @Howard:虽然你对这个问题的回答很好,但我认为这个问题解决了更深层次的问题。
  • 我确实在他工作的大楼里度过了相当多的时间,是的 :-) 不过,我还没有真正见过 STL 先生本人。不过,我会跟进他们为什么std::move 没有在这里内联;我真的觉得这很令人费解。我下周末都在休假,所以我要过几周才能回复你。

标签: c++ optimization reference c++11 move-semantics


【解决方案1】:

std::move(build_report()) 是完全没有必要的:build_report() 已经是一个右值表达式(它是一个按值返回对象的函数的调用),所以如果 std::wstring 有一个移动构造函数(它确实)。

另外,当你返回一个局部变量时,如果它是具有移动构造函数的类型,它会被移动,所以不会复制,句号。

report 声明为对象或常量引用之间不应该有任何功能差异;在这两种情况下,您最终都会得到一个对象(可以绑定 report 引用的命名 report 对象或未命名对象)。

【讨论】:

  • 在存在 (N)RVO 的情况下,另外两个在功能上应该相同 - 有趣。我没有考虑过。我来看看生成的程序集。我会回来的!
  • 我稍微编辑了一下:这两者之间不应该有任何区别:实际上,在这两种情况下,您都必须在report 的范围内拥有一些对象;唯一的“区别”是 report 是对象的名称还是绑定到该对象的引用......从性能的角度来看,两者之间应该没有区别。
  • 请在问题中查看我的 Edit 2
【解决方案2】:

我不确定这是否是标准化的(正如 Nicol 所说,所有优化都取决于编译器),但我听说 STL 谈论这个并且(至少在 MSVC 中),RVO 发生在 之前 其他任何东西。因此,如果有机会应用 RVO,那么您无需采取任何行动即可实现。其次,当你返回一个临时值时,你不必写std::move(我认为这实际上在标准中),因为返回值将被隐式地视为右值。

结果是:不要猜测编译器,只需编写看起来最自然的代码,它就会给你最好的结果。

【讨论】:

  • 在我的 Edit 2 中可以看到明确调用 std::move 甚至会完全阻止 (N)RVO。
  • @Johann:酷。我想这表明仅当您直接返回裸临时文件时才考虑 RVO。不尝试超越语言的另一个理由!
【解决方案3】:

如果有的话,有什么方案可以确定性地选择这些选项?

没有,也永远不会有。

编译器不是必需来进行任何类型的优化。你唯一能做的就是编译一些代码,看看另一端会出现什么。

您最终将获得的最多的是一般启发式,一种社区共识,人们说,“对于大多数编译器来说,X 似乎工作得最快。”但仅此而已。随着编译器跟上 C++0x 的速度并实现成熟,这将需要

【讨论】:

    猜你喜欢
    • 2013-12-19
    • 2012-07-05
    • 2012-05-18
    • 2018-08-03
    • 2014-01-05
    • 1970-01-01
    • 2021-12-22
    • 1970-01-01
    • 2023-03-26
    相关资源
    最近更新 更多