42

私は質問に答えて、コンパイラが戻り値の最適化 (RVO)を実行すると確信していたので、大きな型に対して値渡しを推奨していました。しかしその後、Visual Studio 2013 が私のコードで RVO を実行していないことが指摘されました。

ここで、Visual Studio が RVO を実行できないという質問を見つけましたが、その場合、結論は、本当に重要な場合は Visual Studio が RVO を実行するということのようです。私の場合、それ重要です。プロファイリング結果で確認したパフォーマンスに大きな影響を与えます。簡略化されたコードは次のとおりです。

#include <vector>
#include <numeric>
#include <iostream>

struct Foo {
  std::vector<double> v;
  Foo(std::vector<double> _v) : v(std::move(_v)) {}
};

Foo getBigFoo() {
  std::vector<double> v(1000000);
  std::iota(v.begin(), v.end(), 0);  // Fill vector with non-trivial data

  return Foo(std::move(v));  // Expecting RVO to happen here.
}

int main() {
  std::cout << "Press any key to start test...";
  std::cin.ignore();

  for (int i = 0; i != 100; ++i) {  // Repeat test to get meaningful profiler results
    auto foo = getBigFoo();
    std::cout << std::accumulate(foo.v.begin(), foo.v.end(), 0.0) << "\n";
  }
}

コンパイラが からの戻り値の型で RVO を実行することを期待していgetBigFoo()ます。Fooしかし、代わりにコピーしているようです。

コンパイラが のコピー コンストラクタを作成することは承知していFooます。また、準拠している C++11 コンパイラとは異なり、Visual StudioFoo. RVO は C++98 の概念であり、移動セマンティクスなしで動作します。

問題は、この場合に Visual Studio 2013 が戻り値の最適化を実行しない正当な理由があるかということです。

私はいくつかの回避策を知っています。の移動コンストラクターを定義できますFoo

Foo(Foo&& in) : v(std::move(in.v)) {}

これは問題ありませんが、move-constructors を持たないレガシー型がたくさんあります。これらの型で RVO に依存できることを知っておくとよいでしょう。また、一部の型は本質的にコピー可能ですが、移動はできません。

RVO から NVRO (戻り値の最適化という名前) に変更すると、Visual Studio最適化を実行するように見えます。

  Foo foo(std::move(v))
  return foo;

NVRO は RVO よりも信頼性が低いと思っていたので、これは興味深いことです。

さらに興味深いのは、のコンストラクターを変更して、以下Fooを作成して埋める場合vectorです。

  Foo(size_t num) : v(num) {
    std::iota(v.begin(), v.end(), 0);  // Fill vector with non-trivial data
  }

それを移動する代わりに、RVOを実行しようとすると動作します:

Foo getBigFoo() {
  return Foo(1000000);
}

これらの回避策のいずれかを使用して喜んでいますが、RVO が将来このように失敗する時期を予測できるようにしたいと考えています。

編集: @dyp からのより簡潔なライブ デモ

Edit2:なぜ私はちょうど書かないのreturn v;ですか?

まず、それは役に立ちません。プロファイラーの結果は、Visual Studio 2013 は、私が書いただけでもベクターをコピーすることを示してreturn v;います。この特定のコードを実際に修正しようとしているわけではありません。RVO が失敗する理由を理解しようとしているので、将来いつ失敗するかを予測できます。この特定の例を記述するより簡潔な方法であることは事実ですが、追加のコンストラクターパラメーターがあるreturn v;場合など、単に記述できない場合がたくさんあります。Foo

4

1 に答える 1