web-dev-qa-db-ja.com

最新のC ++でパフォーマンスを無料で取得できますか?

C++ 11/14は、単にC++ 98コードをコンパイルする場合でも、パフォーマンスを向上できると言われています。場合によっては、右辺値コンストラクターが自動的に生成されるか、現在STLの一部であるため、正当化は通常、移動セマンティクスのラインに沿っています。今、私はこれらのケースが以前に実際にRVOまたは類似のコンパイラ最適化によってすでに処理されていたかどうか疑問に思っています。

私の質問は、新しい言語機能をサポートするコンパイラーを使用することで、修正なしでより高速に実行されるC++ 98コードの実際の例を提供できるかどうかです。標準準拠のコンパイラはコピーの省略を行う必要がないことを理解しており、そのため、移動セマンティクスによって速度が向上する可能性がありますが、そうでない場合は病理学的なケースが少ないことを確認したいと思います。

編集:ちょうど明確にするために、新しいコンパイラが古いコンパイラよりも速いかどうかを尋ねているのではなく、コンパイラフラグに-std = c ++ 14を追加するコードがあれば、それは高速に実行されます(コピーは避けますが、移動セマンティクス以外の何かを考え出すことができます、私も興味があるでしょう)

203
alarge

C++ 11としてC++ 03コンパイラを再コンパイルすると、実際には実装の品質とは無関係の無制限のパフォーマンスが向上する5つの一般的なカテゴリを認識しています。これらはすべて、移動セマンティクスのバリエーションです。

std::vector再割り当て

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.Push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

fooのバッファーがC++ 03で再割り当てされるたびに、vector内のbarがすべてコピーされます。

C++ 11では、代わりにbar::datasを移動しますが、これは基本的に無料です。

この場合、これはstdコンテナーvector内の最適化に依存します。以下のすべてのケースで、stdコンテナの使用は、コンパイラをアップグレードするときにC++ 11で「自動的に」効率的なmoveセマンティクスを持つC++オブジェクトであるためです。 stdコンテナを含むオブジェクトをブロックしないオブジェクトも、自動改良されたmoveコンストラクターを継承します。

NRVO障害

NRVO(名前付き戻り値の最適化)が失敗すると、C++ 03ではコピー時にフォールバックし、C++ 11では移動時にフォールバックします。 NRVOの失敗は簡単です。

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.Push_back(i);
  return v;
}

あるいは:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

関数には、戻り値と2つの異なる値の3つの値があります。 Elisionを使用すると、関数内の値を戻り値と「マージ」できますが、相互にはマージできません。両方とも、互いにマージしないと戻り値とマージできません。

基本的な問題は、NRVO省略が脆弱であり、returnサイトの近くにない変更を含むコードは、診断を出力せずにその場所で突然パフォーマンスが大幅に低下する可能性があることです。ほとんどのNRVO障害の場合、C++ 11はmoveになり、C++ 03はコピーになります。

関数の引数を返す

ここではエリシオンも不可能です。

std::set<int> func(std::set<int> in){
  return in;
}

c ++ 11ではこれは安価です。C++ 03ではコピーを回避する方法はありません。パラメータと戻り値の有効期間と場所は呼び出しコードによって管理されるため、関数の引数を戻り値で省略できません。

ただし、C++ 11は一方から他方に移動できます。 (あまりおもちゃではない例では、setに対して何かが行われるかもしれません)。

Push_backまたはinsert

最後に、コンテナーへの省略は行われませんが、C++ 11は右辺値移動挿入演算子をオーバーロードし、コピーを保存します。

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.Push_back( whatever("some long string goes here", 3) );

c ++ 03では、一時的なwhateverが作成され、それがベクトルvにコピーされます。 2 std::stringバッファーが割り当てられ、それぞれ同じデータが使用され、1つが破棄されます。

C++ 11では、一時的なwhateverが作成されます。 whatever&&Push_backオーバーロードは、その後movesを一時的にベクトルvにオーバーロードします。 1つのstd::stringバッファーが割り当てられ、ベクターに移動されます。空のstd::stringは破棄されます。

割り当て

以下の@ Jarod42の回答から盗まれました。

割り当てでは省略は発生しませんが、移動元からは発生します。

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

here some_functionは、除外する候補を返しますが、オブジェクトを直接構築するためには使用されないため、省略できません。 C++ 03では、上記の結果、一時の内容がsome_valueにコピーされます。 C++ 11では、some_valueに移動されますが、これは基本的に無料です。


上記のすべての効果を得るには、移動コンストラクタと割り当てを合成するコンパイラが必要です。

MSVC 2013は、stdコンテナーに移動コンストラクターを実装しますが、型の移動コンストラクターを合成しません。

そのため、std::vectorsなどを含む型は、MSVC2013ではそのような改善は受けませんが、MSVC2015ではそれらの改善が始まります。

clangとgccには、暗黙の移動コンストラクターが実装されてから長い間あります。 Intelの2013コンパイラは、-Qoption,cpp,--gen_move_operationsを渡すと、移動コンストラクターの暗黙的な生成をサポートします(MSVC2013との互換性を保つために、デフォルトでは実行しません)。

220

次のようなものがある場合:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

C++ 03でコピーを取得しましたが、C++ 11で移動割り当てを取得しました。その場合、無料で最適化できます。

45
Jarod42