c++ - loop - std:: for_each range




forループのpIter!= cont.end()のパフォーマンス (5)

私は最近Herb Sutterが "Exceptional C ++"を使いこなしていました。そして、私は彼がItem 6 - Temporary Objectsに与えた特別な勧告について深刻な疑念を持っています。

彼は、次のコードで不要な一時オブジェクトを見つけることを提案しています:

string FindAddr(list<Employee> emps, string name) 
{
  for (list<Employee>::iterator i = emps.begin(); i != emps.end(); i++)
  {
    if( *i == name )
    {
      return i->addr;
    }
  }
  return "";
}

例の1つとして、繰り返しのたびに一時オブジェクトが作成されるため、ループの前にemps.end()値を事前に計算することをお勧めします。

ほとんどのコンテナ(リストを含む)では、end()を呼び出すと、構築および破棄が必要な一時オブジェクトが返されます。 値が変更されないため、すべてのループ反復で再計算(および再構築と再作成)は、不必要に非効率的で美的でもありません。 値は一度だけ計算し、ローカルオブジェクトに格納して再利用する必要があります。

彼は次のように置き換えることを提案しています:

list<Employee>::const_iterator end(emps.end());
for (list<Employee>::const_iterator i = emps.begin(); i != end; ++i)

私にとっては、これは不要な合併症です。 醜い型の宣言をコンパクトautoで置き換えたとしても、彼はまだ1行ではなく2行のコードを取得します。 さらに、彼は外側の範囲にこのend変数を持っています。

私は実際にはここでconst_iteratorを使用しているので、現代のコンパイラがこのコードを最適化すると確信していました。ループコンテンツが何らかの形でコンテナにアクセスしているかどうかを確認するのは簡単です。 コンパイラは過去13年間でよりスマートになったよね?

とにかく、ほとんどの場合、 i != emps.end()で最初のバージョンを優先します。ここでは、パフォーマンスについてあまり心配していません。 しかし、私はこれが、最適化のためにコンパイラに頼ることのできる種類のものなのかどうか、確かに知りたいですか?

更新

この役に立たないコードをより良くする方法に関するあなたの提案に感謝します。 ご注意ください、私の質問は、プログラミング技術ではなく、コンパイラです。 今のところ唯一関連する答えはNPEとElliohです。


stdアルゴリズムを使用する

彼は当然です。 呼び出しendは一般的に悪い一時的なオブジェクトをインスタンス化して破棄することができます。

もちろん、コンパイラはこれを多くの場合に最適化できます。

より良い、より堅牢なソリューションがありますループをカプセル化します

あなたが与えた例は、実際にはstd::find 、返す値を与えたり、取ります。 他の多くのループもstdアルゴリズムを持っています。例えば、私のユーティリティライブラリにはtransform_if実装があります。

だから、関数内のループを隠し、 const&const&end 。 あなたの例と同じ修正ですが、はるかにクリーンです。


vectorのようなコンテナはvariableを返します。これは変数end()へのポインタを格納します。 もしあなたがend()呼び出しに関するいくつかのルックアップなどを行うコンテナを書いていれば、

for (list<Employee>::const_iterator i = emps.begin(), end = emps.end(); i != end; ++i)
{
...
}

スピード


いくつかのことを考えてみましょう。まず、イテレータを作成するコスト(リリースモードでは未チェックのアロケータ)は最小限です。 それらは通常、ポインタの周りのラッパーです。 チェックされたアロケータ(デフォルトはVS)では、コストがかかるかもしれませんが、本当にパフォーマンスが必要な場合は、未確認のアロケータでリビルドをテストしてください。

コードはあなたが投稿したものと同じくらい醜い必要はありません:

for (list<Employee>::const_iterator it=emps.begin(), end=emps.end(); 
                                    it != end; ++it )

1つまたは他のアプローチを使用するかどうかの主要な決定は、コンテナに適用されている操作の観点から行う必要があります。 コンテナがサイズを変更している場合は、各反復でendイテレータを再計算することができます。 そうでない場合は、上記のコードのように、一度事前計算して再利用できます。


パフォーマンスが本当に必要な場合は、光沢のある新しいC ++ 11コンパイラで書き出します。

for (const auto &i : emps) {
    /* ... */
}

はい、これは舌のようなものです(一種)。 ここのハーブの例は古くなっています。 しかし、あなたのコンパイラはそれをまだサポートしていないので、本当の質問に行きましょう:

これは、私が最適化するためにコンパイラに依存することができる構造の一種ですか?

私の経験則は、コンパイラの作家が私より賢明だということです。 より大きな影響を及ぼす他のものを最適化することを選択する可能性があるため、コンパイラを使用してコードを最適化することはできません。 確実に知る唯一の方法はシステム上コンパイラーで両方のアプローチを試してみて、何が起こるかを見ることです。 プロファイラの結果を確認してください。 .end()への呼び出しが出てきたら、それを別の変数に保存してください。 それ以外の場合は、心配しないでください。


私はg++ 4.7.2-O3 -std=c++11を使って以下のややハックのあるコードをコンパイルし、両方の関数で同じアセンブリを得ました:

#include <list>
#include <string>

using namespace std;

struct Employee: public string { string addr; };

string FindAddr1(list<Employee> emps, string name)
{
  for (list<Employee>::const_iterator i = emps.begin(); i != emps.end(); i++)
  {
    if( *i == name )
    {
      return i->addr;
    }
  }
  return "";
}

string FindAddr2(list<Employee> emps, string name)
{
  list<Employee>::const_iterator end(emps.end());
  for (list<Employee>::const_iterator i = emps.begin(); i != end; i++)
  {
    if( *i == name )
    {
      return i->addr;
    }
  }
  return "";
}

いずれにしても、2つのバージョンの選択は、主に可読性の根拠に基づいて行われるべきだと私は思っています。 データをプロファイリングせずに、このようなマイクロ最適化は時期尚早に見えます。





temporary-objects