C ++で配列またはstd :: vectorsを使用して、パフォーマンスのギャップは何ですか?


207

C ++コースでは、新しいプロジェクトでC ++配列を使用しないことを推奨しています。私が知る限り、Stroustroup自身は配列を使用しないことを推奨しています。しかし、パフォーマンスに大きな違いはありますか?


2
なぜパフォーマンスのギャップがあると思いますか。
マーティンヨーク

99
通常、より優れた機能を備えていると、パフォーマンスが最悪になるためです。
tunnuz 2008

19
時期尚早の最適化については同意しますが、より優れたストレージ方法を前もって選択することは、非常に理にかなっています。多くの場合、現実の世界では、コードを出荷する必要があり、次の製品が開発されて、最適化ステップが実行されることはありません。
Ant

132
人々が「時期尚早の最適化!」誰かがパフォーマンスに関する簡単な質問をしているときはいつでも!質問に答えて、人々が時期尚早に何かをしていると時期尚早に考えないでください。
d7samurai

4
@ d7samaurai:同意する、まだ誰も使っていないのを見たことがあるint main(int argc, const std::vector<string>& argv)
Mark K Cowan

回答:


188

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に通知するためのクレジット)。


77
いまいましいAT&T構文を発明したのは誰ですか?私が知っている場合にのみ... :)
Mehrdad Afshari

4
これは、Visual C ++コンパイラには当てはまりません。しかし、GCCにとってはそうです。
TOTO

5
私の答えの要点は、ベクトルは対応するポインター操作よりも遅い必要はないということです。もちろん、それができる(簡単すぎて、デバッグモードを有効に有効にすることにより達成するために):)こと
ヨハネス・シャウブ- litb

18
+1の「ベクトルのインデックス付けは、ポインタのインデックス付けと同じです」その他の結論についても同様です。
Nawaz 2013年

3
@ Piotr99私はあなたと議論するつもりはありませんが、高水準言語を学習した後にアセンブリを学ぶとき、Intel構文は、いくつかの後方、接頭辞(番号)、接尾辞(指示)、および不明瞭(メモリへのアクセス)よりもはるかに意味があります)AT&T構文の性質。
Cole Johnson

73

マイクロオプティマイザの人々のための前文

覚えておいてください:

「プログラマーは、プログラムの重要ではない部分の速度を考えたり、心配したりすることに膨大な時間を費やします。デバッグやメンテナンスを考えると、これらの効率化の試みは、実際には強い否定的な影響を与えます。 97%の時間:早期の最適化がすべての悪の根源です。しかし、その重要な3%で機会を逃してはなりません。

(完全な引用の変態のおかげで)

それが低レベルであると想定されているのでそれがより速いと信じているからといって、ベクトル(または何でも)の代わりにC配列を使用しないでください。あなたは間違っているでしょう。

デフォルトのベクトル(または必要に応じて調整された安全なコンテナー)を使用し、プロファイラーが問題だと言った場合は、より優れたアルゴリズムを使用するか、コンテナーを変更して、最適化できるかどうかを確認します。

つまり、元の質問に戻ることができます。

静的/動的配列?

C ++配列クラスは、低レベルのC配列よりも動作が優れています。これは、C ++配列が自分自身についてよく知っていて、C配列ではできない質問に答えられるためです。彼らは自分自身をきれいにすることができます。さらに重要なことに、これらは通常、テンプレートまたはインライン化、あるいはその両方を使用して記述されます。つまり、デバッグの多くのコードに表示されるものは、リリースビルドで生成されるコードがほとんどまたはまったく生成されないため、組み込みの安全性の低い競合との違いはありません。

全体として、2つのカテゴリに分類されます。

動的配列

malloc-ed / new-ed配列へのポインターを使用すると、せいぜいstd :: vectorバージョンと同じくらい高速になり、はるかに安全性が低くなります(litbの投稿を参照))。

したがって、std :: vectorを使用します。

静的配列

静的配列を使用するのが最善です。

  • std :: arrayバージョンと同じくらい高速
  • 安全性が大幅に低下します。

std :: arrayを使用してください

初期化されていないメモリ

時には、使用してvectorいるので代わりに生のバッファのは、目に見えるコストが発生vector建設でバッファを初期化します、それは置き換えコードがなかったが述べたように、バーニー彼の中での答え

これが当てはまる場合は、のunique_ptr代わりにを使用して処理できます。vectorまたは、コードラインで例外でない場合は、実際にbuffer_ownerそのメモリを所有するクラスを作成し、それに簡単かつ安全にアクセスできるようにします。サイズ変更(realloc?を使用)などのボーナス、または必要なものは何でも。


1
静的配列にも対処していただきありがとうございます。パフォーマンス上の理由からメモリを動的に割り当てることが許可されていない場合、std :: vectorは役に立ちません。
トム

10
「静的配列を使用すると、せいぜいboost :: arrayバージョンと同じくらい速くなる」と言うと、どれだけ偏っているのかがわかります。Boost:arrayは、静的配列のようにせいぜい高速にできます。
TOTO

3
@toto:誤解です:「静的配列の使用は最高です((boost :: arrayバージョンと同じくらい速い)&&(安全性がかなり低い))」と読む必要があります。これを明確にするために投稿を編集します。ちなみに、疑いの恩恵をありがとうございます。
paercebal 2009

1
std :: arrayはどうですか?
ポール、2014年

4
常に完全な引用を表示します。「プログラマーは、プログラムの重要ではない部分の速度を考えたり、心配したりすることに膨大な時間を費やします。デバッグやメンテナンスを考えると、これらの効率化の試みは、実際には強い否定的な影響を与えます。 97%の確率:早期の最適化がすべての悪の根源です。しかし、その重要な3%で機会を逃してはなりません。」それ以外の場合は、意味のないサウンドバイトになります。
変態

32

ベクトルは内部で配列になっています。性能は同じです。

パフォーマンスの問題が発生する可能性のある場所の1つは、最初にベクターのサイズを正しく設定していないことです。

ベクトルがいっぱいになると、それ自体がサイズ変更されます。つまり、新しい配列の割り当て、n個のコピーコンストラクター、n個のデストラクタコール、配列の削除の順になります。

コンストラクト/デストラクトが高価な場合は、ベクターを最初から正しいサイズにすることをお勧めします。

これを示す簡単な方法があります。作成/破棄/コピー/割り当てのタイミングを示す単純なクラスを作成します。これらのもののベクターを作成し、ベクターの後端にプッシュし始めます。ベクターがいっぱいになると、ベクターのサイズが変更されると、一連のアクティビティが発生します。次に、予想される要素数に合わせたサイズのベクトルを使用して、もう一度試してください。違いがわかります。


4
Pendantry:パフォーマンスは同じ大きなOを持っています。std :: vectorは少しの簿記を行いますが、これはおそらく少し時間がかかります。OTOH、あなたはあなた自身の動的配列を転がすときに同じ簿記のほとんどをすることになります。
dmckee ---元モデレーターの子猫

はい、わかりました。しかし、彼の質問の主旨は、パフォーマンスの違いは何であるかということでした.....私はそれに対処しようとしました。
EvilTeach 2008

push_backを呼び出した場合、Gccのstd :: vectorは実際に容量を1つずつ増やします。
bjhend 2012

3
@bjhendでは、gccのstd::vectorサウンドは標準に準拠していませんか?標準では、vector::push_back一定の複雑さを償却する必要があるとpush_back思います。再割り当てを考慮すると、容量をそれぞれ1ずつ増やすとn ^ 2複雑になります。-とのある種の指数容量の増加を想定するpush_backinsert、に失敗するreserveと、ベクターコンテンツのコピーが最大で一定の係数で増加します。1.5の指数ベクトル成長係数は、失敗した場合のコピー数が最大3倍になることを意味しreserve()ます。
Yakk-Adam Nevraumont

3
@bjhendあなたは間違っている。規格は指数関数的な成長を禁止しています:§23.2.3パラグラフ16は、「表101は、いくつかのタイプのシーケンスコンテナーに提供される操作をリストしますが、他のタイプのコンテナーには提供されません。償却された一定の時間がかかるようにそれらを実装するものとします。」(テーブル101は、push_backが含まれているテーブルです)。FUDの拡散を停止してください。主流の実装はこの要件に違反していません。Microsoftの標準C ++ライブラリは1.5倍の係数で成長し、GCCは2倍の係数で成長します。
R.マルティーニョフェルナンデス2013

27

Mehrdadが言ったことに応答するには:

ただし、配列が必要な場合もあります。配列を必要とする低レベルのコード(つまり、アセンブリ)または古いライブラリとインターフェイスする場合、ベクトルを使用できない場合があります。

まったく真実ではありません。次を使用する場合、ベクトルは配列/ポインタにうまく分解されます。

vector<double> vector;
vector.push_back(42);

double *array = &(*vector.begin());

// pass the array to whatever low-level code you have

これは、すべての主要なSTL実装で機能します。次の標準では、それが機能する必要があります(今日は問題なく機能します)。


1
現在の規格はそのようなことを言っていません。それは暗示され、継続的なストレージとして実装されます。しかし、標準は単にそれが(イテレータを使用した)ランダムアクセスコンテナであることを述べています。次の標準は明確になります。
フランククルーガー

1
&* v.begin()は、反復子の逆参照の結果に&演算子を適用するだけです。逆参照はANYタイプを返すことができます。アドレス演算子を使用すると、再びANYタイプを返すことができます。標準では、これをメモリの連続した領域へのポインタとして定義していません。
フランククルーガー

15
規格の1998年の原文は確かにそれを必要としませんでしたが、これに対処する2003年の補遺があったので、それは実際に規格によってカバーされています。herbsutter.wordpress.com/2008/04/07/…–
Nemanja Trifunovic

2
C ++ 03は、サイズの範囲内であれば&v[n] == &v[0] + n有効であることを明示的に述べていnます。このステートメントを含む段落は、C ++ 11でも変更されていません。
bjhend 2012

2
なぜstd :: vector :: data()を使用しないのですか?
ポール、2014年

15

C ++ 11でプレーン配列を使用する理由はさらに少なくなります。

配列には、その機能に応じて、最も速いものから最も遅いものまで、3種類の配列があります(もちろん、実装の品質により、リストのケース3でも非常に高速になります)。

  1. コンパイル時に既知のサイズの静的。---std::array<T, N>
  2. 動的に実行時にサイズが判明し、サイズ変更されることはありません。ここでの典型的な最適化は、配列をスタックに直接割り当てることができる場合です。- 利用できません。多分dynarray C ++ TSでのC ++ 14の後に。CにはVLAがあります
  3. 実行時に動的でサイズ変更可能。---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];た場合の利点は、サイズを変更できることと、単純な配列のようにポインタに合わせて減衰しないことです。

前述の標準ライブラリタイプを使用して、配列がポインタに減衰するのを回避 します。同じ機能のセットを使用する場合、デバッグ時間を節約でき、パフォーマンスはプレーン配列とまったく同じです。


2
std :: dynarray。n3690に対する国別機関のコメントを確認した後、このライブラリコンポーネントはC ++ 14ワーキングペーパーから別の技術仕様に投票されました。このコンテナは、n3797現在のドラフトC ++ 14の一部ではありません。en.cppreference.com/w/cpp/container/dynarray
Mohamed El-Nakib 2014年

1
非常に良い答えです。簡潔で要約しますが、他よりも詳細です。
Mohamed El-Nakib 2014年

6

STLを使用してください。パフォーマンスの低下はありません。アルゴリズムは非常に効率的であり、ほとんどの人が考えないような種類の詳細をうまく処理します。


5

STLは非常に最適化されたライブラリです。実際、高いパフォーマンスが必要になる可能性のあるゲームでSTLを使用することも推奨されています。配列はエラーが発生しやすく、日常のタスクで使用できません。今日のコンパイラーも非常にスマートで、STLを使用して優れたコードを本当に生成できます。何をしているのかわかっている場合、STLは通常、必要なパフォーマンスを提供します。たとえば、ベクターを必要なサイズに初期化することにより(最初から知っている場合)、基本的に配列のパフォーマンスを実現できます。ただし、それでも配列が必要な場合があります。配列を必要とする低レベルのコード(つまり、アセンブリ)または古いライブラリとインターフェイスする場合、ベクトルを使用できない場合があります。


4
そのベクトルが連続していることを考えると、配列を必要とするライブラリーとのインターフェースは依然として非常に簡単です。
グレッグロジャース

はい。ただし、ベクターの内部のものをいじくり回したい場合は、ベクターを使用してもあまりメリットはありません。ちなみに、キーワードは「ないかもしれない」でした。
Mehrdad Afshari

3
ベクトルを使用できない場所が1つだけわかっています。サイズが0の場合、&a [0]または&* a.begin()は機能しません。c ++ 1xは、要素を保持する内部バッファーを返すa.data()関数を導入することでそれを修正します
Johannes Schaub-litb

私がそれを書いたときに私の心の中で特定のシナリオはスタックベースの配列でした。
Mehrdad Afshari、

1
vec.data()データまたはvec.size()サイズのC:とのインターフェイスベクトルまたは連続するコンテナー。とても簡単です。
ヘルマン・Diago

5

duliの貢献について

結論として、整数の配列は整数のベクトルよりも高速です(この例では5倍)。ただし、配列とベクトルは、より複雑な/整列されていないデータの場合と同じ速度です。


3

ソフトウェアをデバッグモードでコンパイルする場合、多くのコンパイラーはベクターのアクセサー関数をインライン化しません。これにより、パフォーマンスが問題となる状況では、stlベクトルの実装が大幅に遅くなります。また、割り当てられたメモリの量をデバッガーで確認できるため、コードのデバッグが容易になります。

最適化モードでは、stlベクトルが配列の効率に近づくことを期待します。これは、多くのベクトルメソッドがインライン化されたためです。


これは言及することが重要です。デバッグSTLのプロファイリングは非常に遅くなります。そして、それが人々がSTLの処理を遅くする理由の1つです。
Erik Aronesty 2014

3

初期化されていないバッファーstd::vectorが必要な場合(たとえば、の宛先として使用する場合)、vsとraw配列を使用すると、明らかにパフォーマンスに影響があります。デフォルトコンストラクタを使用して、そのすべての要素を初期化します。生の配列ではできません。memcpy()std::vector

C ++の仕様のためにstd:vector取って、コンストラクタcountの引数を(それは三番目の形式です)状態:

`オプションでユーザー指定のアロケーターallocを使用して、さまざまなデータソースから新しいコンテナーを構築します。

3)countが挿入されたTのインスタンスでコンテナを構築します。コピーは作成されません。

複雑

2-3)線形のカウント

生の配列では、この初期化コストは発生しません。

参照してくださいどのように私は、そのすべての要素を初期化するためのstd ::ベクトル<>を避けることができますか?


2

2つのパフォーマンスの違いは、実装に大きく依存します。不適切に実装されたstd :: vectorを最適な配列実装と比較すると、配列は優先されますが、向きを変えてベクトルが優先されます...

リンゴとリンゴを比較する限り(配列とベクトルの両方に要素の数が固定されているか、両方が動的にサイズ変更される)、STLコーディングの実践に従っている限り、パフォーマンスの違いは無視できると思います。標準のC ++コンテナーを使用すると、標準のC ++ライブラリの一部である事前にロールされたアルゴリズムを使用することもでき、それらのほとんどは、自分で作成した同じアルゴリズムの平均的な実装よりもパフォーマンスが優れている可能性が高いことを忘れないでください。

そうは言っても、適切なデバッグモードを備えたほとんどのSTL実装は、標準のコンテナーを操作するときに人々が犯す典型的な間違いを少なくとも強調/解決できるため、デバッグSTLを使用したデバッグシナリオでベクターが勝ちます。

ああ、配列とベクトルは同じメモリレイアウトを共有しているため、ベクトルを使用して、基本的な配列を期待するレガシーCまたはC ++コードにデータを渡すことができることを忘れないでください。ただし、そのシナリオではほとんどの賭けがオフになっており、未加工のメモリを再び処理していることに注意してください。


1
パフォーマンス要件(O(1)ルックアップと挿入)を満たすためには、動的配列を使用してstd :: vector <>を実装する必要があると思います。確かにこれはそれを行う明白な方法です。
dmckee ---元モデレーターの子猫

パフォーマンス要件だけでなく、ストレージを隣接させるという要件も含まれます。悪いベクトルの実装は、配列とAPIの間のインダイレクションのレイヤーが多すぎます。良好なベクターの実装では、SIMD、ループ、等に使用される、インラインコードを可能にする
最大Lybbert

説明されているような悪いベクトルの実装は、標準に準拠していません。間接参照が必要な場合std::dequeは、使用される場合があります。
Phil1970、2016年

1

サイズを動的に調整する必要がない場合は、容量を節約するためのメモリオーバーヘッドがあります(1つのポインター/ size_t)。それでおしまい。


1

インライン関数内のインライン関数内にベクターアクセスがあり、コンパイラーがインライン化するものを超えて、関数呼び出しを強制するという特別なケースがあるかもしれません。それは心配する価値がないほどまれなことです-一般的に私はlitbに同意します

まだ誰もこれについて言及していないことに驚いています。問題であることが証明されるまでパフォーマンスを気にしないでください。


1

主な関心事はパフォーマンスではなく安全性だと私は主張します。配列を使用すると、多くの間違いを犯す可能性があります(たとえば、サイズ変更を検討します)。ベクトルを使用すると、多くの苦痛を軽減できます。


1

ベクトルは配列のサイズを含んでいるため、配列よりも少しだけ多くのメモリを使用します。また、プログラムのハードディスクサイズ、おそらくプログラムのメモリフットプリントも増加します。これらの増加はわずかですが、組み込みシステムで作業している場合は問題になることがあります。これらの違いが重要なほとんどの場所は、C ++ではなくCを使用する場所です。


2
これが問題になる場合は、動的サイズの配列を使用していないことは明らかなので、配列のサイズを変更する必要はありません。(もしそうなら、あなたはどういうわけかサイズを保存しているでしょう)。したがって、私が間違っていない限り、boost :: arrayを使用することもできます。
Arafangion

1

次の簡単なテスト:

C ++アレイとベクターのパフォーマンステストの説明

「ベクトルと配列/ポインタに対する基本的なインデックス付け、逆参照、および増分操作のために生成されたアセンブリコードの比較」からの結論と矛盾します。

配列とベクトルの間には違いがあるはずです。テストはそう言っています...試してみてください、コードはそこにあります...


1

配列は実際にはベクトルよりも優れている場合があります。常に固定長のオブジェクトセットを操作する場合は、配列の方が適しています。次のコードスニペットを検討してください。

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 ++に投稿したものです)。


1

ベクトルを使用して多次元の動作を表す場合、パフォーマンスに影響があります。

2d +ベクトルはパフォーマンスに影響を与えますか?

要点は、各サブベクトルにサイズ情報が含まれることによるオーバーヘッドが少しあり、データのシリアル化が必ずしも必要ではないことです(多次元のc配列の場合と同様)。このシリアライゼーションの欠如は、マイクロ最適化の機会を超える可能性があります。多次元配列を使用している場合は、単にstd :: vectorを拡張して、独自のget / set / resize bits関数をロールするのが最善の方法です。


0

固定長配列(例えば仮定すると、int* v = new int[1000];std::vector<int> v(1000);のサイズで、v1000年に固定されたままされている)、本当に重要(または私は同様のジレンマにあったときに、少なくとも私には大事)のみのパフォーマンス考慮すると、ANへのアクセス速度であります素子。STLのベクトルコードを調べたところ、次のことがわかりました。

const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }

この関数は、コンパイラによって確実にインライン化されます。したがって、でvその要素にアクセスすることだけを計画している限り、operator[]実際にはパフォーマンスに違いはないはずです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.