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


205

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

私の質問は、新しい言語機能をサポートするコンパイラを使用して、変更なしでより高速に実行されるC ++ 98コードの実際の例を教えてもらえるかどうかです。コピーの省略を行うのに標準準拠のコンパイラーは必要ないことを理解しています。そのため、移動のセマンティクスによって速度が向上する可能性があります。

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


3
コピーの省略と戻り値の最適化は、コピーコンストラクターを使用して新しいオブジェクトを構築するときに実行されることに注意してください。ただし、コピー代入演算子では、コピーの省略はありません(コンパイラーは、一時的でない既に作成されたオブジェクトをどう処理するかわからないため、どうすればよいでしょうか)。したがって、その場合、C ++ 11/14は、移動代入演算子を使用する可能性を与えることで、大きな勝利を収めます。しかし、あなたの質問については、C ++ 11/14コンパイラーでコンパイルした場合、C ++ 98コードの方が高速ではないと思います。コンパイラーが新しいため、おそらく高速です。
vsoftco 2014

27
また、標準ライブラリを使用するコードは、C ++ 98と完全に互換性を持たせたとしても、潜在的に高速になります。これは、C ++ 11/14では、可能な場合、基になるライブラリが内部移動セマンティクスを使用するためです。したがって、C ++ 98とC ++ 11/14で同一に見えるコードは、ベクトル、リストなどの標準ライブラリオブジェクトを使用し、セマンティクスを移動することで違いが生じる場合は、後者の場合(おそらく)高速になります。
vsoftco 2014

1
@vsoftco、それは私が言及していた種類の状況ですが、例を思い付くことができませんでした:コピーコンストラクターを定義する必要がある場合に覚えていることから、移動コンストラクターは自動的に生成されないため、 RVOが常に機能する非常に単純なクラス。例外は、STLコンテナーと関連するもので、rvalueコンストラクターがライブラリーのインプリメンターによって生成される場合があります(つまり、移動を使用するためにコードを変更する必要はありません)。
2014

クラスは、コピーコンストラクターがないために単純である必要はありません。C ++は値のセマンティクスで成功し、コピーコンストラクター、代入演算子、デストラクターなどは例外です。
sp2danny 2014

1
@エリックリンクありがとうございました。ただし、すぐに調べた結果、速度の利点は、ほとんどの場合、std::moveコンストラクターの追加と移動(既存のコードの変更が必要になる)に由来するようです。私の質問に本当に関連している唯一のことは、「再コンパイルするだけですぐに速度が向上する」という文でした。これは、例では裏付けられていません(質問で行ったように、同じスライドでSTLについて言及していますが、具体的なものはありません) )。私はいくつかの例を求めていました。スライドを間違って読んでいる場合は、お知らせください。
2014

回答:


221

C ++ 03コンパイラをC ++ 11として再コンパイルすると、実装の品質に実質的に関係のない無限のパフォーマンスの向上を引き起こす可能性がある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コンテナーを使用するのはmove、コンパイラーをアップグレードすると、C ++ 11で「自動的に」効率的なセマンティクスを持つ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オーバーロードはmoveその一時的なものをベクターに入れvます。1つのstd::stringバッファーが割り当てられ、ベクターに移動されます。空std::stringは破棄されます。

割り当て

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

割り当てではエリシオンは発生しませんが、移動元は発生します。

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

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


上記の効果を十分に発揮するには、moveコンストラクターと割り当てを合成するコンパイラーが必要です。

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

そのstd::vectorため、sなどを含む型は、MSVC2013ではそのような改善は得られませんが、MSVC2015ではそれらの改善が開始されます。

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


1
@はいはい。ただし、移動コンストラクターがコピーコンストラクターより何倍も効率的であるためには、通常、リソースをコピーする代わりに移動する必要があります。独自の移動コンストラクターを作成せずに(そしてC ++ 03プログラムを再コンパイルするだけで)、stdライブラリコンテナーはすべてmove「無料」のコンストラクターで更新され、(ブロックしていない場合は)上記のオブジェクトを使用する構成体(および上記のオブジェクト)は、さまざまな状況で自由移動構築を開始します。これらの状況の多くは、C ++ 03の省略によってカバーされます。すべてではありません。
Yakk-Adam Nevraumont 2014

5
それは悪いオプティマイザ実装です。そして、返される異なる名前のオブジェクトは重複する存続期間を持たないため、RVOは理論的にはまだ可能です。
Ben Voigt 2014

2
@alargeライフタイムが重複している2つのオブジェクトを3番目のオブジェクトに省略できても、お互いに省略できない場合など、省略が失敗する場所があります。次に、C ++ 11では移動が必要で、C ++ 03ではコピーが必要です(as-ifは無視)。Elisionは実際には壊れやすいことがよくあります。std上記のコンテナーの使用は、C ++ 03を再コンパイルするときにC ++ 11で「無料」で取得するコピータイプに安価に移動できることが主な理由です。これvector::resizeは例外ですmove。C++ 11 で使用されます。
Yakk-Adam Nevraumont 2014

27
移動セマンティクスである一般的なカテゴリは1つだけで、その特殊なケースは5つだけです。
Johannes Schaub-litb 2014

3
@sebro私が理解しているように、「プログラムが何千もの多くのキロバイト割り当てを割り当てないので、代わりにポインタを移動する」とは十分ではないと考えています。あなたは時間制限のある結果を求めています。マイクロベンチマークは、パフォーマンスの改善を証明するものであり、基本的に行っていないことを証明するものです。実業界のタスクプロファイリングでプロファイリングされている多種多様な業界の数百の実世界アプリケーションが不足していることは、実際には証明されていません。「フリーパフォーマンス」について漠然とした主張をして、C ++ 03とC ++ 11でのプログラムの動作の違いについて具体的な事実を述べました。
Yakk-Adam Nevraumont

46

次のようなものがあれば:

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

// some code

v = foo();

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


4
@Yakk:割り当てでコピー省略がどのように発生しますか?
Jarod42 14

2
@ Jarod42私はまた、左側がすでに構築されており、右からリソースを盗んだ後にコンパイラが「古い」データをどう処理するかを知るための合理的な方法がないため、割り当てではコピーの省略は不可能だと思います。ハンドサイド。しかし、私は間違っているかもしれません。私は答えを一度と永遠に見つけたいです。オブジェクトは「フレッシュ」であり、古いデータをどう処理するかを決定する問題がないため、コピーの省略は構成をコピーするときに意味があります。私の知る限り、唯一の例外はこれです。「割り当ては、as-ifルールに基づいてのみ削除できます」
vsoftco

4
グッドC ++ 03のコードは、すでにを通じて、この場合に移動をしたfoo().swap(v);
ベンフォークト

@BenVoigt確かに、すべてのコードが最適化されているわけではありません。
Yakk-Adam Nevraumont 2014

@BenVoigtが言うように、コピー省略は割り当てで機能します。より良い用語はRVO(戻り値の最適化)であり、foo()がそのように実装されている場合にのみ機能します。
DrumM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.