C ++ 11の 'auto'を使用してパフォーマンスを改善できますか?


230

autoC ++ 11の型が正確さと保守性を向上させる理由がわかります。パフォーマンスを向上させることもできる(私はHerb Sutterによるほぼ常に自動)と読みましたが、良い説明がありません。

  • autoパフォーマンスを改善するにはどうすればよいですか?
  • 誰でも例を挙げられますか?

5
ガジェットからウィジェットへの偶発的な暗黙の変換の回避について説明しているherbsutter.com/2013/06/13/…を参照してください。これは一般的な問題ではありません。
Jonathan Wakely、2015

42
パフォーマンスの向上として、「意図せずに悲観的になる可能性を低くする」ことを受け入れますか?
5gon12eder

1
コードのパフォーマンスは将来的にクリーンアップされる可能性があります
Croll

短い答えが必要です。あなたが良ければ、いいえ。それは「noobish」の間違いを防ぐことができます。C ++には学習曲線があり、結局それを作らない人を殺します。
Alec Teal

回答:


309

auto暗黙的な暗黙の変換を回避することによりパフォーマンスを向上させることができます。私が説得力があると思う例は次のとおりです。

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}

バグを参照してください?ここでは、const参照によってマップ内のすべてのアイテムをエレガントに取得し、意図を明確にするために新しいrange-for式を使用していると考えていますが、実際にはすべての要素をコピーしています。これは、そうでstd::map<Key, Val>::value_typestd::pair<const Key, Val>ないからですstd::pair<Key, Val>。したがって、(暗黙的に)以下の場合:

std::pair<Key, Val> const& item = *iter;

既存のオブジェクトへの参照を取得してそのままにする代わりに、型変換を行う必要があります。暗黙的な変換が利用可能である限り、別のタイプのオブジェクト(または一時)へのconst参照を使用できます。例:

int const& i = 2.0; // perfectly OK

型変換は、a const Keyをに変換できるのと同じ理由で許可された暗黙の変換ですがKey、それを可能にするために新しい型の一時を構築する必要があります。したがって、効果的にループは次のようになります。

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(もちろん、実際には__tmpオブジェクトはなく、説明のためだけにあります。実際、名前のない一時ファイルはitem、その存続期間中のみバインドされます)。

に変更するだけです:

for (auto const& item : m) {
    // do stuff
}

大量のコピーを保存しただけです-参照される型は初期化子の型と一致するため、一時的な変換や変換は必要なく、直接参照するだけで済みます。


19
@Barryコンパイラがをstd::pair<const Key, Val> const &として処理しようとすることについて不平を言う代わりに喜んでコピーを作成する理由を説明できますstd::pair<Key, Val> const &か?C ++ 11の新機能ですauto。どのように範囲を設定し、これを実行するかは不明です。
Agop、2015

@バリー説明ありがとうございます。それは私が欠けていたものです-何らかの理由で、あなたは一時的なものへの一定の参照を持つことができないと思いました。しかし、もちろん、それは可能です-それは、そのスコープの終わりに存在しなくなるだけです。
Agop

@barry私はあなたを理解しますが、問題は、autoパフォーマンスを向上させるために使用する理由のすべてをカバーする答えがないということです。なので、以下に自分の言葉で書きます。
Yakk-Adam Nevraumont、2015

38
私はまだこれが「autoパフォーマンスを向上させる」ことの証明だとは思いません。これは、「autoパフォーマンスを破壊するプログラマーのミスを防ぐのに役立つ」単なる例です。私は、この2つの間に微妙ではあるが重要な違いがあると考えています。それでも、+ 1。
オービットのライトネスレース

70

auto初期化式の型を推定するので、型変換は含まれません。テンプレート化されたアルゴリズムと組み合わせると、これは、特に名前を付けることができないタイプの式を扱う場合に、自分でタイプを作成する場合よりも直接的な計算ができることを意味します。

典型的な例は(ab)usingから来ていstd::functionます:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);

cmp2andを使用するとcmp3、アルゴリズム全体で比較呼び出しをインライン化できます。一方、std::functionオブジェクトを作成する場合、呼び出しをインライン化できないだけでなく、関数ラッパーの型消去された内部で多相ルックアップを実行する必要があります。

このテーマのもう1つのバリエーションは、次のように言うことができます。

auto && f = MakeAThing();

これは常に参照であり、関数呼び出し式の値にバインドされ、追加のオブジェクトを構築することはありません。戻り値の型がわからない場合は、のような方法で(おそらく一時的なものとして)新しいオブジェクトを作成する必要がありますT && f = MakeAThing()。(さらに、auto &&戻り値の型が可動ではなく、戻り値がprvalueである場合でも機能します。)


したがって、これは「型消去を回避する」理由autoです。他のバリアントは「偶発的なコピーを避ける」ですが、装飾が必要です。なぜautoそこにタイプを入力するよりもスピードが速いのですか?(答えは「型を間違えたら黙って変換する」だと思います)バリーの答えのよく説明されていない例ですよね?つまり、2つの基本的なケースがあります。タイプの消去を回避するautoと、誤って変換されるサイレントタイプのエラーを回避するautoです。どちらも実行時間のコストがあります。
Yakk-Adam Nevraumont 2015

2
「呼び出しをインライン化できないだけでなく」-なぜそれなのでしょうか。データは、関連の専門if分析を流した後、あなたは、原則として、何かの防止に呼び出しがdevirtualizedされていることを意味するかstd::bindstd::functionそしてstd::stable_partition、すべてのインライン化されていますか?それとも実際には、C ++コンパイラーは混乱を整理するのに十分積極的にインライン化しないでしょうか?
スティーブジェソップ2015

@SteveJessop:ほとんどが後者- std::functionコンストラクターを通過した後、特に小さな関数の最適化を使用すると、実際の呼び出しを確認するのが非常に複雑になります(したがって、実際には非仮想化は必要ありません)。もちろん、原則としてすべてが
現状の

41

2つのカテゴリがあります。

auto型の消去を回避できます。名前付けできないタイプ(ラムダなど)と、ほとんど名前付けできないタイプ(結果std::bindまたは他の式テンプレートのようなもの)があります。

なしautoでは、データを消去して、次のようなものにする必要がありstd::functionます。型の消去にはコストがかかります。

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};

task1型消去オーバーヘッド-ヒープ割り当ての可能性、インライン化の難しさ、仮想関数テーブルの呼び出しオーバーヘッド。 task2なし。ラムダは、型を消去せずに格納するために、自動または他の形式の型の演繹が必要です。他のタイプは非常に複雑で、実際に必要なだけです。

次に、タイプを間違える可能性があります。場合によっては、間違ったタイプは一見完璧に機能しますが、コピーが発生します。

Foo const& f = expression();

expression()返されたBar const&場合、Barまたは場合によってはBar&Fooから構築できる場合にコンパイルされBarます。テンポラリFooが作成されてにバインドされf、そのライフタイムはなくなるまで延長されfます。

プログラマーはBar const& fそこにコピーを作成するつもりだったのではないかもしれませんが、関係なくコピーが作成されます。

最も一般的な例は、のタイプであり*std::map<A,B>::const_iterator、そうではありstd::pair<A const, B> const&ませんstd::pair<A,B> const&が、エラーは、パフォーマンスを暗黙のうちに犠牲にするエラーのカテゴリーです。std::pair<A, B>からを作成できstd::pair<const A, B>ます。(マップのキーはconstです。編集は悪い考えです)

@Barryと@KerrekSBの両方が最初に、これらの2つの原則を回答で示しました。これは、2つの問題を1つの回答で強調する試みであり、例を中心とするのではなく、問題を目的とした表現です。


9

既存の3つの答えは、使用auto「意図せずに悲観化する可能性を低くし」、効果的に「パフォーマンスを向上」させる例を示しています。

コインには裏返しがあります。auto基本オブジェクトを返さない演算子を持つオブジェクトで使用すると、誤った(コンパイル可能で実行可能な)コードになる可能性があります。たとえば、この質問でauto、Eigenライブラリを使用して、異なる(誤った)結果がどのように与えられたかを尋ねます。つまり、次の行

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;

異なる出力が発生しました。確かに、これは主にEigensの遅延評価によるものですが、そのコードは(ライブラリー)ユーザーに対して透過的である必要があります。

ここではパフォーマンスに大きな影響はありませんが、auto意図しない悲観化を回避するために使用することは、時期尚早の最適化として分類されるか、少なくとも間違っています;)。


1
逆の質問を追加しました:stackoverflow.com/questions/38415831/...
レオン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.