C ++コースでは、新しいプロジェクトでC ++配列を使用しないことを推奨しています。私が知る限り、Stroustroup自身は配列を使用しないことを推奨しています。しかし、パフォーマンスに大きな違いはありますか?
int main(int argc, const std::vector<string>& argv)
C ++コースでは、新しいプロジェクトでC ++配列を使用しないことを推奨しています。私が知る限り、Stroustroup自身は配列を使用しないことを推奨しています。しかし、パフォーマンスに大きな違いはありますか?
int main(int argc, const std::vector<string>& argv)
回答:
C ++配列new
を動的配列とともに使用することは避けてください。サイズを追跡する必要があるという問題があり、それらを手動で削除して、あらゆる種類のハウスキーピングを行う必要があります。
範囲のチェックがないため、スタックで配列を使用することもお勧めしません。配列を渡すと、そのサイズに関する情報(配列からポインターへの変換)が失われます。boost::array
その場合は、C ++配列を小さなクラスでラップし、size
それを反復する関数と反復子を提供するものを使用する必要があります。
今度はstd :: vectorとネイティブC ++配列(インターネットから取得):
// Comparison of assembly code generated for basic indexing, dereferencing,
// and increment operations on vectors and arrays/pointers.
// Assembly code was generated by gcc 4.1.0 invoked with g++ -O3 -S on a
// x86_64-suse-linux machine.
#include <vector>
struct S
{
int padding;
std::vector<int> v;
int * p;
std::vector<int>::iterator i;
};
int pointer_index (S & s) { return s.p[3]; }
// movq 32(%rdi), %rax
// movl 12(%rax), %eax
// ret
int vector_index (S & s) { return s.v[3]; }
// movq 8(%rdi), %rax
// movl 12(%rax), %eax
// ret
// Conclusion: Indexing a vector is the same damn thing as indexing a pointer.
int pointer_deref (S & s) { return *s.p; }
// movq 32(%rdi), %rax
// movl (%rax), %eax
// ret
int iterator_deref (S & s) { return *s.i; }
// movq 40(%rdi), %rax
// movl (%rax), %eax
// ret
// Conclusion: Dereferencing a vector iterator is the same damn thing
// as dereferencing a pointer.
void pointer_increment (S & s) { ++s.p; }
// addq $4, 32(%rdi)
// ret
void iterator_increment (S & s) { ++s.i; }
// addq $4, 40(%rdi)
// ret
// Conclusion: Incrementing a vector iterator is the same damn thing as
// incrementing a pointer.
注:あなたが配列を割り当てる場合new
と非クラス(無地のようなオブジェクトに割り当てるint
ユーザー定義のコンストラクタなし)やクラスをして、あなたの要素が最初に初期化したくない、使用してnew
いるため-allocated配列すると、パフォーマンス上の利点を持つことができますstd::vector
初期化するすべての要素に構築時のデフォルト値(intの場合は0など)(@bernieに通知するためのクレジット)。
覚えておいてください:
「プログラマーは、プログラムの重要ではない部分の速度を考えたり、心配したりすることに膨大な時間を費やします。デバッグやメンテナンスを考えると、これらの効率化の試みは、実際には強い否定的な影響を与えます。 97%の時間:早期の最適化がすべての悪の根源です。しかし、その重要な3%で機会を逃してはなりません。
(完全な引用の変態のおかげで)
それが低レベルであると想定されているのでそれがより速いと信じているからといって、ベクトル(または何でも)の代わりにC配列を使用しないでください。あなたは間違っているでしょう。
デフォルトのベクトル(または必要に応じて調整された安全なコンテナー)を使用し、プロファイラーが問題だと言った場合は、より優れたアルゴリズムを使用するか、コンテナーを変更して、最適化できるかどうかを確認します。
つまり、元の質問に戻ることができます。
C ++配列クラスは、低レベルのC配列よりも動作が優れています。これは、C ++配列が自分自身についてよく知っていて、C配列ではできない質問に答えられるためです。彼らは自分自身をきれいにすることができます。さらに重要なことに、これらは通常、テンプレートまたはインライン化、あるいはその両方を使用して記述されます。つまり、デバッグの多くのコードに表示されるものは、リリースビルドで生成されるコードがほとんどまたはまったく生成されないため、組み込みの安全性の低い競合との違いはありません。
全体として、2つのカテゴリに分類されます。
malloc-ed / new-ed配列へのポインターを使用すると、せいぜいstd :: vectorバージョンと同じくらい高速になり、はるかに安全性が低くなります(litbの投稿を参照))。
したがって、std :: vectorを使用します。
静的配列を使用するのが最善です。
std :: arrayを使用してください。
時には、使用してvector
いるので代わりに生のバッファのは、目に見えるコストが発生vector
建設でバッファを初期化します、それは置き換えコードがなかったが述べたように、バーニー彼の中での答え。
これが当てはまる場合は、のunique_ptr
代わりにを使用して処理できます。vector
または、コードラインで例外でない場合は、実際にbuffer_owner
そのメモリを所有するクラスを作成し、それに簡単かつ安全にアクセスできるようにします。サイズ変更(realloc
?を使用)などのボーナス、または必要なものは何でも。
ベクトルは内部で配列になっています。性能は同じです。
パフォーマンスの問題が発生する可能性のある場所の1つは、最初にベクターのサイズを正しく設定していないことです。
ベクトルがいっぱいになると、それ自体がサイズ変更されます。つまり、新しい配列の割り当て、n個のコピーコンストラクター、n個のデストラクタコール、配列の削除の順になります。
コンストラクト/デストラクトが高価な場合は、ベクターを最初から正しいサイズにすることをお勧めします。
これを示す簡単な方法があります。作成/破棄/コピー/割り当てのタイミングを示す単純なクラスを作成します。これらのもののベクターを作成し、ベクターの後端にプッシュし始めます。ベクターがいっぱいになると、ベクターのサイズが変更されると、一連のアクティビティが発生します。次に、予想される要素数に合わせたサイズのベクトルを使用して、もう一度試してください。違いがわかります。
std::vector
サウンドは標準に準拠していませんか?標準では、vector::push_back
一定の複雑さを償却する必要があるとpush_back
思います。再割り当てを考慮すると、容量をそれぞれ1ずつ増やすとn ^ 2複雑になります。-とのある種の指数容量の増加を想定するpush_back
とinsert
、に失敗するreserve
と、ベクターコンテンツのコピーが最大で一定の係数で増加します。1.5の指数ベクトル成長係数は、失敗した場合のコピー数が最大3倍になることを意味しreserve()
ます。
Mehrdadが言ったことに応答するには:
ただし、配列が必要な場合もあります。配列を必要とする低レベルのコード(つまり、アセンブリ)または古いライブラリとインターフェイスする場合、ベクトルを使用できない場合があります。
まったく真実ではありません。次を使用する場合、ベクトルは配列/ポインタにうまく分解されます。
vector<double> vector;
vector.push_back(42);
double *array = &(*vector.begin());
// pass the array to whatever low-level code you have
これは、すべての主要なSTL実装で機能します。次の標準では、それが機能する必要があります(今日は問題なく機能します)。
&v[n] == &v[0] + n
有効であることを明示的に述べていn
ます。このステートメントを含む段落は、C ++ 11でも変更されていません。
C ++ 11でプレーン配列を使用する理由はさらに少なくなります。
配列には、その機能に応じて、最も速いものから最も遅いものまで、3種類の配列があります(もちろん、実装の品質により、リストのケース3でも非常に高速になります)。
std::array<T, N>
dynarray
C ++ TSでのC ++ 14の後に。CにはVLAがありますstd::vector<T>
以下のための1の要素の固定数、使用したプレーンな静的アレイstd::array<T, N>
C ++ 11インチ
以下のため2。固定サイズ配列は、実行時に指定したが、それはそのサイズを変更しないだろう、そこの議論はC ++ 14であるが、それは技術仕様に移動し、最終的にはC ++ 14から作られています。
3. std::vector<T>
通常、ヒープ内のメモリを要求します。これを使用std::vector<T, MyAlloc<T>>
すると、カスタムアロケーターを使用して状況を改善することができますが、パフォーマンスに影響を与える可能性があります。と比較しT mytype[] = new MyType[n];
た場合の利点は、サイズを変更できることと、単純な配列のようにポインタに合わせて減衰しないことです。
前述の標準ライブラリタイプを使用して、配列がポインタに減衰するのを回避 します。同じ機能のセットを使用する場合、デバッグ時間を節約でき、パフォーマンスはプレーン配列とまったく同じです。
STLは非常に最適化されたライブラリです。実際、高いパフォーマンスが必要になる可能性のあるゲームでSTLを使用することも推奨されています。配列はエラーが発生しやすく、日常のタスクで使用できません。今日のコンパイラーも非常にスマートで、STLを使用して優れたコードを本当に生成できます。何をしているのかわかっている場合、STLは通常、必要なパフォーマンスを提供します。たとえば、ベクターを必要なサイズに初期化することにより(最初から知っている場合)、基本的に配列のパフォーマンスを実現できます。ただし、それでも配列が必要な場合があります。配列を必要とする低レベルのコード(つまり、アセンブリ)または古いライブラリとインターフェイスする場合、ベクトルを使用できない場合があります。
vec.data()
データまたはvec.size()
サイズのC:とのインターフェイスベクトルまたは連続するコンテナー。とても簡単です。
結論として、整数の配列は整数のベクトルよりも高速です(この例では5倍)。ただし、配列とベクトルは、より複雑な/整列されていないデータの場合と同じ速度です。
ソフトウェアをデバッグモードでコンパイルする場合、多くのコンパイラーはベクターのアクセサー関数をインライン化しません。これにより、パフォーマンスが問題となる状況では、stlベクトルの実装が大幅に遅くなります。また、割り当てられたメモリの量をデバッガーで確認できるため、コードのデバッグが容易になります。
最適化モードでは、stlベクトルが配列の効率に近づくことを期待します。これは、多くのベクトルメソッドがインライン化されたためです。
初期化されていないバッファーstd::vector
が必要な場合(たとえば、の宛先として使用する場合)、vsとraw配列を使用すると、明らかにパフォーマンスに影響があります。デフォルトコンストラクタを使用して、そのすべての要素を初期化します。生の配列ではできません。memcpy()
std::vector
C ++の仕様のためにstd:vector
取って、コンストラクタcount
の引数を(それは三番目の形式です)状態:
`オプションでユーザー指定のアロケーターallocを使用して、さまざまなデータソースから新しいコンテナーを構築します。
3)countが挿入されたTのインスタンスでコンテナを構築します。コピーは作成されません。
複雑
2-3)線形のカウント
生の配列では、この初期化コストは発生しません。
2つのパフォーマンスの違いは、実装に大きく依存します。不適切に実装されたstd :: vectorを最適な配列実装と比較すると、配列は優先されますが、向きを変えてベクトルが優先されます...
リンゴとリンゴを比較する限り(配列とベクトルの両方に要素の数が固定されているか、両方が動的にサイズ変更される)、STLコーディングの実践に従っている限り、パフォーマンスの違いは無視できると思います。標準のC ++コンテナーを使用すると、標準のC ++ライブラリの一部である事前にロールされたアルゴリズムを使用することもでき、それらのほとんどは、自分で作成した同じアルゴリズムの平均的な実装よりもパフォーマンスが優れている可能性が高いことを忘れないでください。
そうは言っても、適切なデバッグモードを備えたほとんどのSTL実装は、標準のコンテナーを操作するときに人々が犯す典型的な間違いを少なくとも強調/解決できるため、デバッグSTLを使用したデバッグシナリオでベクターが勝ちます。
ああ、配列とベクトルは同じメモリレイアウトを共有しているため、ベクトルを使用して、基本的な配列を期待するレガシーCまたはC ++コードにデータを渡すことができることを忘れないでください。ただし、そのシナリオではほとんどの賭けがオフになっており、未加工のメモリを再び処理していることに注意してください。
std::deque
は、使用される場合があります。
サイズを動的に調整する必要がない場合は、容量を節約するためのメモリオーバーヘッドがあります(1つのポインター/ size_t)。それでおしまい。
主な関心事はパフォーマンスではなく安全性だと私は主張します。配列を使用すると、多くの間違いを犯す可能性があります(たとえば、サイズ変更を検討します)。ベクトルを使用すると、多くの苦痛を軽減できます。
ベクトルは配列のサイズを含んでいるため、配列よりも少しだけ多くのメモリを使用します。また、プログラムのハードディスクサイズ、おそらくプログラムのメモリフットプリントも増加します。これらの増加はわずかですが、組み込みシステムで作業している場合は問題になることがあります。これらの違いが重要なほとんどの場所は、C ++ではなくCを使用する場所です。
次の簡単なテスト:
「ベクトルと配列/ポインタに対する基本的なインデックス付け、逆参照、および増分操作のために生成されたアセンブリコードの比較」からの結論と矛盾します。
配列とベクトルの間には違いがあるはずです。テストはそう言っています...試してみてください、コードはそこにあります...
配列は実際にはベクトルよりも優れている場合があります。常に固定長のオブジェクトセットを操作する場合は、配列の方が適しています。次のコードスニペットを検討してください。
int main() {
int v[3];
v[0]=1; v[1]=2;v[2]=3;
int sum;
int starttime=time(NULL);
cout << starttime << endl;
for (int i=0;i<50000;i++)
for (int j=0;j<10000;j++) {
X x(v);
sum+=x.first();
}
int endtime=time(NULL);
cout << endtime << endl;
cout << endtime - starttime << endl;
}
ここで、Xのベクトルバージョンは
class X {
vector<int> vec;
public:
X(const vector<int>& v) {vec = v;}
int first() { return vec[0];}
};
Xの配列バージョンは次のとおりです。
class X {
int f[3];
public:
X(int a[]) {f[0]=a[0]; f[1]=a[1];f[2]=a[2];}
int first() { return f[0];}
};
内側のループで毎回「新規」のオーバーヘッドを回避しているため、main()の配列バージョンはより高速になります。
(このコードは私がcomp.lang.c ++に投稿したものです)。
ベクトルを使用して多次元の動作を表す場合、パフォーマンスに影響があります。
要点は、各サブベクトルにサイズ情報が含まれることによるオーバーヘッドが少しあり、データのシリアル化が必ずしも必要ではないことです(多次元のc配列の場合と同様)。このシリアライゼーションの欠如は、マイクロ最適化の機会を超える可能性があります。多次元配列を使用している場合は、単にstd :: vectorを拡張して、独自のget / set / resize bits関数をロールするのが最善の方法です。
固定長配列(例えば仮定すると、int* v = new int[1000];
対std::vector<int> v(1000);
のサイズで、v
1000年に固定されたままされている)、本当に重要(または私は同様のジレンマにあったときに、少なくとも私には大事)のみのパフォーマンス考慮すると、ANへのアクセス速度であります素子。STLのベクトルコードを調べたところ、次のことがわかりました。
const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }
この関数は、コンパイラによって確実にインライン化されます。したがって、でv
その要素にアクセスすることだけを計画している限り、operator[]
実際にはパフォーマンスに違いはないはずです。