web-dev-qa-db-ja.com

C ++ 11の「auto」を使用するとパフォーマンスが向上しますか?

C++ 11のauto型が正確性と保守性を向上させる理由がわかります。パフォーマンスを向上させることもできると読んだことがあります( Almost Always Auto by Herb Sutter).

  • autoはどのようにパフォーマンスを改善できますか?
  • 誰でも例を挙げることができますか?
225
DaBrain

autoは、サイレントな暗黙の変換を回避するによってパフォーマンスを向上させることができます。説得力のある例は次のとおりです。

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

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

バグがありますか?ここでは、const参照によってマップ内のすべてのアイテムをエレガントに取得し、新しいrange-for式を使用して意図を明確にしていると考えていますが、実際にはevery要素をコピーしています。これは、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

型変換は、const KeyKeyに変換できるのと同じ理由で、暗黙的な変換が許可されていますが、それを可能にするために新しい型のテンポラリを作成する必要があります。したがって、ループは事実上次のことを行います。

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
}

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

302
Barry

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

典型的な例は、([ab))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?);

cmp2cmp3を使用すると、アルゴリズム全体で比較呼び出しをインライン化できますが、std::functionオブジェクトを作成する場合、呼び出しをインライン化できないだけでなく、関数ラッパーの型消去された内部のポリモーフィックルックアップ。

このテーマの別のバリエーションは、あなたが言うことができるということです:

auto && f = MakeAThing();

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

69
Kerrek SB

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

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

autoがないと、データをstd::functionなどのように入力する必要があります。型の消去にはコストがかかります。

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

task1には、型消去のオーバーヘッドがあります。ヒープ割り当ての可能性、インライン化の困難さ、および仮想関数テーブル呼び出しのオーバーヘッドです。 task2には何もありません。 Lambdas need autoまたは型消去なしで保存する他の形式の型推定。他のタイプは非常に複雑なため、実際に必要なだけです。

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

Foo const& f = expression();

expression()Bar const&またはBarまたはBar&を返す場合にコンパイルされます。FooBarから構築できます。一時的な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つの回答で強調するための単なる試みであり、例を中心とするのではなく、問題を目的とした文言を使用しています。

既存の3つの回答は、autoの使用が役立つ例を示しています 「意図せずにペシマイズする可能性を低くします」 効果的に「パフォーマンスを向上」します。

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

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を使用することは、時期尚早な最適化または少なくとも間違っていると分類される可能性があります;)。

7
Avi Ginsburg