web-dev-qa-db-ja.com

反復子対operator [] / indexによるstd :: vectorへのアクセス速度

たとえば、

std::vector<SomeClass *> v;

私のコードでは、プログラム内の要素に頻繁にアクセスし、それらを前後にループする必要があります。

これら2つの中で最も速いアクセスタイプはどれですか?

イテレーターアクセス:

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i;
std::vector<SomeClass *>::reverse_iterator j;

// i loops forward, j loops backward
for( i = v.begin(), j = v.rbegin(); i != v.end() && j != v.rend(); i++, j++ ){
    // some operations on v items
}

添字アクセス(インデックスによる)

std::vector<SomeClass *> v;
unsigned int i, j, size = v.size();

// i loops forward, j loops backward
for( i = 0, j = size - 1; i < size && j >= 0; i++, j-- ){
    // some operations on v items
}

また、const_iteratorは、ベクトル要素を変更する必要がない場合に、ベクトル要素にアクセスするより高速な方法を提供しますか?

37

パフォーマンスの違いはおそらく無視できるか、まったくありません(コンパイラーはそれらを同一に最適化する場合があります)。プログラムが正しいかどうかなど、他のことを心配する必要があります(遅いが正しいプログラムは、速くて間違ったプログラムよりも優れています)。ただし、ループを変更せずに、基になるコンテナをoperator[]のないコンテナに変更できるなど、イテレータを使用することには他の利点もあります。詳しくは この質問 をご覧ください。

const_iteratorsは、通常のイテレーターと比較して、パフォーマンスの違いがほとんどないか、ごくわずかです。これらは、パフォーマンスのためではなく、変更すべきでないものの変更を防ぐことにより、プログラムの正確性を改善するように設計されています。一般的に、constキーワードについても同様です。

要するに、最適化は次の2つのことが起こるまであなたの関心事ではないはずです:1)実行に気づいたtoo slowと2)ボトルネックのプロファイリング 1)の場合、実行速度が10倍遅くなりましたが、実行されるのは一度だけで0.1ミリ秒かかる場合、誰が気にしますか? 2)では、それが間違いなくボトルネックであることを確認してください。そうしないと、最適化によりパフォーマンスがほぼ測定可能な影響なしになります。

28
AshleysBrain

単純なループベースのベンチマークが満たされました。 VS 2010 SP1(リリース構成)を使用しました。

  1. イテレータを使用する(* it = * it + 1;)
  2. インデックスを使用(vs [i] = vs [i] + 1;)

数十億回の反復で、2番目のアプローチは1%だけ少し高速であることが判明しました。結果(インデックスはイテレータよりわずかに速い)は再現可能ですが、私が言ったように、その差は非常に小さいです。

17
borx

速度が重要な場合は、時間をかけてプロファイラーを実行し、どちらが最適かを確認してください。

関係ない場合は、時期尚早な最適化を実行しているため、実行を停止する必要があります。

5

昨日、[]対イテレータを使用してテストを行いました。コードは、いくつかの要素を含むベクトルを作成し、ベクトルからいくつかの要素を削除します。これは、要素にアクセスするために演算子[]を使用するコードです

  TimeSpent([](){
    std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    for (int i = int(vt.size()) - 1; i >= 0; i--)
    {
      if (vt[i] % 2 == 0)
      {
        //cout << "removing " << vt[i] << endl;
        vt.erase(vt.begin() + i);
      }
    }
  });

次のコードは、イテレーターを使用したベクター要素へのアクセスに関するものです

  TimeSpent([](){
    std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    for (std::vector<int>::iterator num = vt.begin(); num != vt.end();)
    {
      if (*num % 2 == 0)
      {
        num = vt.erase(num);
      }
      else
      {
        ++num;
      }
    }
  });

この関数によって個別に呼び出すことによりテスト済み

void TimeSpent(std::function<void()> func)
{
  const int ONE_MIL = 10000;
  long times = ONE_MIL;
  std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
  while (times > 0)
  {
    func();
    --times;
  }
  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
  cout << "time elapsed : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << endl;
}


テストされた環境は、Visual Studio 2013 proです。バージョン4.5.51650
結果は次のとおりです。
operator []:192
イテレーター:212
概要:ベクトルコンテナーにアクセスすると、演算子[]は反復子より高速です。

3
r0ng

ベクトルイテレータは内部的に(優れたSTL実装で)ポインタとして実装されているため、一般に2つのイディオムのパフォーマンスの違いは無視できるはずです。しかし、これらがyourプラットフォームでどのように機能するかを知りたい場合は、小さなテストプログラムで測定してみませんか?たとえば、実行時間を測定するのに5分以上かかるとは思わない。両方のバリアントで100万回の反復...

2
Péter Török

最適化(-O2)を使用すると、タイミングが改善されます(ほぼ同じになるはずです)。

1
Jay

いつものように、それは依存します。通常、私はあなたがどんな種類の違いも見るとは思わないでしょう、しかしあなただけがあなたのコードをプロファイリングすることによってそれを決定することができます。コンパイラの中には、ベクトル反復子を生のポインタとして実装するものと、実装しないものがあります。また、デバッグビルドでは、一部のコンパイラがチェックイテレータを使用している場合がありますが、これは遅くなる可能性があります。しかし、本番モードでは、違いはありません。それをプロファイリングして見てください。

1
Brian Neal

私は似たようなことに混乱し、パフォーマンスをテストするプログラムを書いた: https://github.com/rajatkhanduja/Benchmarks/blob/master/C%2B%2B/vectorVsArray.cpp

7.7 GB RAMのLinux-i686(64ビットマシン)で、g ++(最適化フラグなし)を使用してサイズ1mのvector <int>の読み取り/書き込みに関連する観察結果を次に示します。

インデックスを使用してベクターに書き込むのにかかった時間。 :11.3909ミリ秒

インデックスを使用してベクターから順番に読み取るのにかかる時間。 :4.09106ミリ秒

インデックスを使用してベクトルからランダムに読み取るのにかかった時間。 :39ミリ秒

反復子を使用してベクターに書き込むために要した時間(順次)。 :24.9949ミリ秒

イテレーターを使用してベクターから読み取るのにかかる時間(順次)。 :18.8049ミリ秒

0
rajatkhanduja

速度の面では、ほぼ同じかもしれません。とにかく、プロファイルとチェックを行うことができます。

少なくとも、使用する変数の数を減らすことができます:)

for( i = 0; i < size ; i++){
    // some operations on v items
    v[i];
    v[size-i+1];
}

const_iterator:Plsは私のQを参照します:A re const_iteratorsより高速ですか?

0
aJ.

私はイテレータに行きますが、最適化するのはループ内でend()を呼び出し、preincrementをpostincrementに変更することです。つまりしたい

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i,ie;
std::vector<SomeClass *>::reverse_iterator j,je;

// i loops forward, j loops backward
for( i=v.begin(),ie=v.end(), j=v.rbegin(),je=v.rend(); i!=ie && j!=je; ++i,++j ){
    // some operations on v items
}

そして、私はそれが時期尚早なマイクロ最適化だとは思わず、ただより良いコードを書いているだけです。効率的なコードの時期尚早なマイクロ最適化を記述し、思考をプロファイリングに置き換えようとするあらゆる試みを呼び出すよりもはるかに邪悪ではありません。