パフォーマンスの面では、何がより速く動作しますか?違いはありますか?プラットフォームに依存していますか?
//1. Using vector<string>::iterator:
vector<string> vs = GetVector();
for(vector<string>::iterator it = vs.begin(); it != vs.end(); ++it)
{
*it = "Am I faster?";
}
//2. Using size_t index:
for(size_t i = 0; i < vs.size(); ++i)
{
//One option:
vs.at(i) = "Am I faster?";
//Another option:
vs[i] = "Am I faster?";
}
テストを書いて調べてみませんか?
編集:悪い-最適化されたバージョンのタイミングを計っていると思っていましたが、そうではありませんでした。 g ++ -O2でコンパイルされた私のマシンでは、イテレータバージョンはoperator []バージョンよりもわずかにslowerですが、おそらくそうではありません。
#include <vector>
#include <iostream>
#include <ctime>
using namespace std;
int main() {
const int BIG = 20000000;
vector <int> v;
for ( int i = 0; i < BIG; i++ ) {
v.Push_back( i );
}
int now = time(0);
cout << "start" << endl;
int n = 0;
for(vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
n += *it;
}
cout << time(0) - now << endl;
now = time(0);
for(size_t i = 0; i < v.size(); ++i) {
n += v[i];
}
cout << time(0) - now << endl;
return n != 0;
}
反復子を使用すると、ポインターが増分され(増分するため)、逆参照されてポインターが逆参照されます。
インデックスを使用する場合、増分も同様に高速である必要がありますが、要素の検索には追加(データポインター+インデックス)とそのポインターの逆参照が含まれますが、違いはわずかです。at()
は、インデックスが境界内にあるかどうかもチェックするため、速度が遅くなる可能性があります。
5M反復、ベクターサイズ10、gcc 4.3.3(-O3)、Linux 2.6.29.1 x86_64のベンチマーク結果:at()
:9158msoperator[]
:4269msiterator
:3914ms
YMMVですが、インデックスを使用するとコードが読みやすく/理解しやすくなる場合は、それを行う必要があります。
インデックス付けが不要な場合は、使用しないでください。イテレータの概念は最善を尽くしています。イテレータは最適化が非常に簡単ですが、直接アクセスには追加の知識が必要です。
インデックスは、直接アクセスを目的としています。括弧とat
メソッドがこれを行います。 at
は、[]
とは異なり、範囲外のインデックスをチェックするため、速度が低下します。
信念は次のとおりです。不要なものを求めないでください。その後、コンパイラは、使用しないものに対して料金を請求しません。
効率を検討しているので、次のバリエーションが潜在的により効率的であることを認識する必要があります。
//1. Using vector<string>::iterator:
vector<string> vs = GetVector();
for(vector<string>::iterator it = vs.begin(), end = vs.end(); it != end; ++it)
{
//...
}
//2. Using size_t index:
vector<string> vs = GetVector();
for(size_t i = 0, size = vs.size(); i != size; ++i)
{
//...
}
end/size関数は、ループを実行するたびにではなく、1回だけ呼び出されるためです。とにかくコンパイラーがこれらの関数をインライン化する可能性がありますが、この方法で確実になります。
ここの他のみんなが言っているように、ベンチマークを行います。
そうは言っても、at()も範囲チェックを行うため、反復子の方が高速であると言えます。つまり、インデックスが範囲外の場合はout_of_range例外をスローします。このチェック自体には、おそらくオーバーヘッドが発生します。
最初のバリアントの方が速いと思います。
ただし、実装に依存します。必ず、独自のコードをプロファイルする必要があります。
独自のコードをプロファイルする理由
これらの要因はすべて結果を変えるため、
at
は、イテレータまたは_operator[]
_よりも常に低速です。
ただし、_operator[]
_対イテレータの場合、次の条件に依存します。
正確さあなたは_operator[]
_を使用しています。
特定のCPUにindexレジスタがあるかどうか(x86では_ESI/EDI
_)。
otherコードも、_operator[]
_に渡される同じインデックスを使用します。
(たとえば、ロックステップで複数の配列をインデックス化していますか?)
その理由は次のとおりです。
あなたが何かをするなら
_std::vector<unsigned char> a, b;
for (size_t i = 0; i < n; ++i)
{
a[13 * i] = b[37 * i];
}
_
このコードは、ループの各反復でmultiplicationoperationを実行するため、イテレータバージョンよりもはるかに遅くなる可能性があります。
同様に、次のようなことを行う場合:
_struct T { unsigned char a[37]; };
std::vector<T> a;
for (size_t i = 0; i < n; ++i)
{
a[i] = foo(i);
}
_
すると、これはおそらくalsoイテレータバージョンよりも遅くなります。なぜならsizeof(T)
は2の累乗ではないため、ループするたびに、(再び)_37
_を掛けています!
CPUにインデックスレジスタがある場合、コードはイテレータではなくインデックスで同様に、またはさらに優れたパフォーマンスを発揮できます。インデックスレジスタを使用すると別のレジスタが解放されますループ。これはnotです。見ればわかるだけです。コードのプロファイルを作成するか、コードを逆アセンブルする必要があります。
複数の配列が同じインデックスを共有できる場合、コードは複数のイテレータをインクリメントするのではなく、oneインデックスをインクリメントするだけでよく、メモリへの書き込みを減らし、一般にパフォーマンスを向上させます。ただし、単一の配列のみを反復処理する場合は、既存のベースポインターにオフセットを追加する必要がないため、反復子は非常に高速になります。
iteratorsであるため、プロファイリングによって切り替えが有益であるというボトルネックに直面するまでは、一般的に、イテレータを優先およびインデックスへのインデックスを使用する必要があります。汎用であり、すでに最速のアプローチである可能性が高い;データがランダムにアドレス可能である必要はないため、必要に応じてコンテナを交換できます。インデックスは、データへの直接アクセスを必要としないため、次の優先ツールです。無効化される頻度は低く、たとえば、問題なくdeque
をvector
に置き換えます。ポインターは最後の手段である必要があり、イテレーターがリリースモードでポチナーにまだ縮退していない場合にのみ有益です。
それは本当にあなたが何をしているのかに依存しますが、イテレータを再宣言し続けなければならない場合、イテレータは非常に遅くなります。私のテストでは、ベクトル配列に対して単純な*を宣言し、それを反復処理するのが、最も高速な反復です。
例えば:
ベクトル反復とパスごとに2つの関数を引き出します。
vector<MyTpe> avector(128);
vector<MyTpe>::iterator B=avector.begin();
vector<MyTpe>::iterator E=avector.end()-1;
for(int i=0; i<1024; ++i){
B=avector.begin();
while(B!=E)
{
float t=B->GetVal(Val1,12,Val2); float h=B->GetVal(Val1,12,Val2);
++B;
}}
ベクターで90クリック(0.090000秒)
しかし、ポインターでそれをした場合...
for(int i=0; i<1024; ++i){
MyTpe *P=&(avector[0]);
for(int i=0; i<avector.size(); ++i)
{
float t=P->GetVal(Val1,12,Val2); float h=P->GetVal(Val1,12,Val2);
}}
ベクターは18回クリック(0.018000秒)
これはおおよそ次と同等です...
MyTpe Array[128];
for(int i=0; i<1024; ++i)
{
for(int p=0; p<128; ++p){
float t=Array[p].GetVal(Val1, 12, Val2); float h=Array[p].GetVal(Val2,12,Val2);
}}
配列は15回クリックしました(0.015000秒)。
Avector.size()の呼び出しを削除すると、時間が同じになります。
最後に、[]で呼び出します
for(int i=0; i<1024; ++i){
for(int i=0; i<avector.size(); ++i){
float t=avector[i].GetVal(Val1,12,Val2); float h=avector[i].GetVal(Val1,12,Val2);
}}
ベクターは33回クリックしました(0.033000秒)
Clock()で計時
インデックスアクセスはバックグラウンドでイテレータを作成するため、最初のものはデバッグモードで高速になりますが、すべてをインライン化するリリースモードでは、違いは無視できるか、nullになります。
このテストコードを使用して、結果を比較できます!ディオそれ!
#include <vector>
#include <iostream>
#include <ctime>
using namespace std;;
struct AAA{
int n;
string str;
};
int main() {
const int BIG = 5000000;
vector <AAA> v;
for ( int i = 0; i < BIG; i++ ) {
AAA a = {i, "aaa"};
v.Push_back( a );
}
clock_t now;
cout << "start" << endl;
int n = 0;
now = clock();
for(vector<AAA>::iterator it = v.begin(); it != v.end(); ++it) {
n += it->n;
}
cout << clock() - now << endl;
n = 0;
now = clock();
for(size_t i = 0; i < v.size(); ++i) {
n += v[i].n;
}
cout << clock() - now << endl;
getchar();
return n != 0;
}
OpenGLコードを最適化しようとしたときにこのスレッドが見つかったため、スレッドが古い場合でも結果を共有したいと考えました。
Background:サイズが6〜12の4つのベクトルがあります。書き込みはコードの先頭で1回のみ発生し、ベクトルの各要素に対して0.1ミリ秒ごとに読み取りが発生します
以下は、最初に使用されたコードの簡略版です。
for(vector<T>::iterator it = someVector.begin(); it < someVector.end(); it++)
{
T a = *it;
// Various other operations
}
この方法を使用したフレームレートは、約7フレーム/秒(fps)でした。
ただし、コードを次のように変更すると、フレームレートはほぼ倍増して15 fpsになりました。
for(size_t index = 0; index < someVector.size(); ++index)
{
T a = someVector[index];
// Various other operations
}
VisualStudio 2005または2008を使用している場合、ベクターから最高のパフォーマンスを得るには、_SECURE_SCL = 0を定義する必要があります。
デフォルトでは、_SECURE_SCLがオンになっているため、包含の反復が大幅に遅くなります。つまり、デバッグビルドではそのままにしておくと、エラーを追跡しやすくなります。マクロはイテレータとコンテナのサイズを変更するため、stlコンテナを共有するすべてのコンパイルユニットで一貫性を保つ必要があります。
唯一の答えはプラットフォームでのテストだと思います。一般に、STLで標準化されている唯一のものは、コレクションが提供するイテレータのタイプとアルゴリズムの複雑さです。
私はそれらの2つのバージョンの間に(ほとんど違いはない)と言います-私が考えることができる唯一の違いは、コードが配列の長さを計算する必要があるときにコードがコレクション全体を反復する必要があることです'長さがベクトル内の変数に格納されているかどうかわからない場合、オーバーヘッドは問題になりません)
「at」で要素にアクセスするには、[]で直接アクセスするよりも少し時間がかかります。これは、ベクトルの境界内にいるかどうかをチェックし、境界外にいる場合は例外をスローするためです([]は通常ポインタ演算を使用して-より高速になります)
違いはごくわずかです。 std :: vectorは、その要素がメモリ内で連続してレイアウトされることを保証します。したがって、ほとんどのstl実装では、反復子をプレーンポインターとしてstd :: vectorに実装します。これを念頭に置いて、2つのバージョンの唯一の違いは、最初のバージョンがポインターをインクリメントし、2番目のバージョンがポインターに追加されるインデックスをインクリメントすることです。私の推測では、2番目の方法は、おそらく非常に高速な(サイクルの観点から)機械命令です。
コンパイラが生成するマシンコードを試してみてください。
ただし、一般的に、本当に重要な場合はプロファイルを作成することをお勧めします。この種の質問について時期尚早に考えても、あまり多くのことは得られません。通常、コードのホットスポットは他の場所にあり、一見すると疑わない場合があります。
元の質問にわずかに接しているだけですが、最速のループは
for( size_t i=size() ; i-- ; ) { ... }
もちろんカウントダウンします。ループに多数の反復がある場合、これにより大幅な節約が得られますが、非常に高速な操作が少数しか含まれていません。
そのため、[]演算子アクセスでは、これはすでに投稿されている多くの例よりも高速です。
デフォルトのmingwコンパイラを使用して、Code :: Blocks v12.11でコンパイルしたコードを次に示します。これは巨大なベクトルを作成し、反復子、at()、およびインデックスを使用して各要素にアクセスします。それぞれは、関数によって最後の要素を呼び出すことによって1回、最後の要素を一時メモリに保存することによって1回ループされます。
タイミングはGetTickCountを使用して行われます。
#include <iostream>
#include <windows.h>
#include <vector>
using namespace std;
int main()
{
cout << "~~ Vector access speed test ~~" << endl << endl;
cout << "~ Initialization ~" << endl;
long long t;
int a;
vector <int> test (0);
for (int i = 0; i < 100000000; i++)
{
test.Push_back(i);
}
cout << "~ Initialization complete ~" << endl << endl;
cout << " iterator test: ";
t = GetTickCount();
for (vector<int>::iterator it = test.begin(); it < test.end(); it++)
{
a = *it;
}
cout << GetTickCount() - t << endl;
cout << "Optimised iterator: ";
t=GetTickCount();
vector<int>::iterator endofv = test.end();
for (vector<int>::iterator it = test.begin(); it < endofv; it++)
{
a = *it;
}
cout << GetTickCount() - t << endl;
cout << " At: ";
t=GetTickCount();
for (int i = 0; i < test.size(); i++)
{
a = test.at(i);
}
cout << GetTickCount() - t << endl;
cout << " Optimised at: ";
t = GetTickCount();
int endof = test.size();
for (int i = 0; i < endof; i++)
{
a = test.at(i);
}
cout << GetTickCount() - t << endl;
cout << " Index: ";
t=GetTickCount();
for (int i = 0; i < test.size(); i++)
{
a = test[i];
}
cout << GetTickCount() - t << endl;
cout << " Optimised Index: ";
t = GetTickCount();
int endofvec = test.size();
for (int i = 0; i < endofvec; i++)
{
a = test[i];
}
cout << GetTickCount() - t << endl;
cin.ignore();
}
これに基づいて、私は個人的に、「最適化された」バージョンは「最適化されていない」イテレータよりも高速であり、直接インデックスよりも遅いvector.at()よりも遅いことを知りました。
自分でコードをコンパイルして実行することをお勧めします。
[〜#〜] edit [〜#〜]:このコードは、C/C++の経験が少ないときに書き戻されました。さらにテストケースは、接尾辞の代わりに接頭辞インクリメント演算子を使用することです。それは実行時間を改善するはずです。