【问题标题】:Likeliness of Named RVO?命名 RVO 的可能性?
【发布时间】:2023-03-25 07:21:01
【问题描述】:

我有一个看起来像这样的函数:

// Fetch 1 MB of data
void GetData(std::vector<char> & outData);

1MB 被夸大了,但我只想指出最好避免不必要的副本。

如果我添加这个重载:

std::vector<char> GetData()
{
    std::vector<char> result;
    GetData(result);
    return result;
}

那么 RVO 参与的可能性有多大?

【问题讨论】:

  • 你可以欺骗编译器这样做,通过返回定义操作符的代理对象到向量引用类型.ymmv

标签: c++ return-value-optimization


【解决方案1】:

对于最合理的最新编译器(例如,VS 2005 或更新版本、gcc 3.4 或更新版本),基本上可以确定。我只说“大多数”是因为我还没有测试所有存在的编译器。我可能在过去 5 年左右看到的每个新编译器都包含它。

【讨论】:

  • Nitpick:VS 2005 也实现了 NRVO (msdn.microsoft.com/en-us/library/ms364057.aspx)。
  • @In silico:已修复。谢谢——我不记得到底是哪个迭代添加了它。
  • VS 不会在调试版本中执行 NRVO 是否令人惊讶?
  • @Matthieu:当您明确要求禁用优化时,如果没有执行优化,我并不觉得特别令人惊讶,但也许这就是我。
  • @Jerry:好吧,这可能会或可能不会令人惊讶,但这可能会对性能造成巨大影响(例如使用 STL 调试功能),从而使调试版本无法使用。如果我没记错的话,gcc 即使在调试中也会执行 URVO,但我不确定 NRVO。
【解决方案2】:

RVO 很可能会发挥作用,因为它是一个非常简单的优化,已经使用了很长一段时间。但是,为了在中等高性能代码中赋予这段代码真正的实用价值,您需要 NRVO。 NRVO 更难遇到,因为它相对较新。然而它是可用的。例如,MS 编译器从 VS2005 开始就实现了它。

【讨论】:

    【解决方案3】:

    我认为对此没有任何标准答案:这取决于您的编译器及其功能。

    如果您为了方便而考虑实现这一点,为什么不直接尝试您的编译器并查看程序集或分析它,看看会发生什么?关于你的编译器实际做什么的经验证据可能比猜测某些编译器可能做什么或可能不做什么要好。

    【讨论】:

      【解决方案4】:

      那么 RVO 参与的可能性有多大?

      思考是软件开发人员的工作,而不是编译器的工作。

      编译器通常经过优化以使好的代码运行良好 - 而不是坏代码。

      就个人而言,我使用第一种形式。通常使用指针而不是引用 - 突出显示参数是输出,而不是输入这一事实。

      【讨论】:

      • 不好的做法,因为 RVO 肯定会启动您今天使用的任何编译器。 NRVO 也很实用。专注于干净的代码,而不是“快速”的代码。 auto x = get_something()type x; get_something(x); 简单得多。如果您的分析器说这是一个问题,那么这个问题不是问题,因为您必须这样做。 (注:“必须”。不是“可以做,猜我应该做。”)
      • 我不介意打字 - 我是一名触控打字员。不幸的是auto x = get_something(); 导致人们经常忘记函数可能正在做一些昂贵的事情。如果类/函数后来移动到库中,那么它经常被忘记更改。 type x; get_something(&amp;x); 保证无论如何都能正常工作。在调试版本中也是如此。
      • @Dummy:现代编译器也在链接时进行优化。在下一个标准 C++0x 中,您只会通过不按值返回(移动语义)来减慢程序的速度。干净的代码总是赢,因为干净的代码很容易快速制作。
      • @GMan:编译器/链接器对共享库或只有标头的第三方静态库几乎无能为力。未发布的 C++0x 的 move c'tor(类似于 .swap())都不会神奇地使已经优化的代码(根本没有 .swap()/等)更快。 (理想情况下,目标代码是等价的。) 主观的:我更喜欢清楚它做什么/如何做的代码。我之前在几家公司中种植了这种编码风格,正是因为这个原因才被选中:提高可读性和减少性能地雷。
      • @GMan:不要误会我的意思。我梦想有一种语言,我不需要考虑所有细节的实现细节。但当前版本的 C++ (C++98) 并非如此。
      【解决方案5】:

      另外,请注意,当您说:

      std::vector<char> GetData() 
      { 
      //   :
          return result; 
      }
      
      vector<char> x = GetData();
      

      在被调用者中,result被复制到一个“返回值”中,然后在调用者中,“返回值”被复制到x中。 NRVO 可以删除其中一个副本,但不能同时删除两者。编译器必须至少调用一次复制 ctor,因为它必须假设复制 ctor 具有必须完成的副作用。

      【讨论】:

      • 我不确定它是否真的可以看到复制 ctor 的定义(以及它可能递归调用的所有内容)。但我同意你的观点,在实践中,使用单独的编译单元,你是对的。
      • 这不是真的。标准中甚至还提供了一个示例来说明这种确切情况。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多