web-dev-qa-db-ja.com

C ++で配列またはstd :: vectorを使用すると、パフォーマンスのギャップはどうなりますか?

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

189
tunnuz

newでC++配列を使用する(つまり、動的配列を使用する)ことは避けてください。サイズを追跡する必要がある問題があり、それらを手動で削除し、あらゆる種類のハウスキーピングを行う必要があります。

範囲のチェックがないため、スタックでの配列の使用もお勧めできません。また、配列を渡すと、サイズに関する情報(配列からポインターへの変換)が失われます。その場合は、boost::arrayを使用する必要があります。これは、C++配列を小さなクラスにラップし、size関数とそれを反復処理する反復子を提供します。

std :: vector vs. native C++ array(インターネットから取得):

// 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 arrayを使用すると、要素を最初に初期化するために、std::vectorが構築時にすべての要素をデフォルト値(たとえば、intの場合は0)に初期化するため、パフォーマンス上の利点があります(覚えているために@bernieにクレジット)。

183

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

覚えておいてください:

「プログラマーは、プログラムの重要でない部分の速度を考える、または心配するのに膨大な時間を浪費します。これらの効率化の試みは、デバッグとメンテナンスを考慮すると、実際に強いマイナスの影響を与えます。 97%の時間: 早すぎる最適化はすべての悪の根源です。 それでも、私たちはその重要な3%でチャンスを逃してはなりません。」.

(完全な引用について metamorphosis に感謝)

下位レベルであると想定されているため高速であると思われるという理由だけで、ベクトル(またはその他)の代わりにC配列を使用しないでください。あなたは間違っているでしょう。

デフォルトのベクトル(またはニーズに合わせた安全なコンテナ)を使用し、プロファイラーが問題だと言ったら、より良いアルゴリズムを使用するか、コンテナを変更することで最適化できるかどうかを確認します。

これは、元の質問に戻ることができると言いました。

静的/動的配列?

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

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

動的配列

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

そのため、std :: vectorを使用します。

静的配列

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

  • std :: array versionと同じ速さ
  • そして、はるかに安全ではありません。

std :: array を使用します。

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

時々、生のバッファーの代わりにvectorを使用すると、vectorが構築時にバッファーを初期化するため、目に見えるコストが発生しますが、置き換えられるコードは bernie by by 答え

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

66
paercebal

ベクトルは内部の配列です。パフォーマンスは同じです。

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

ベクトルがいっぱいになると、それ自体のサイズが変更されます。これは、新しい配列の割り当て、n個のコピーコンストラクター、約n個のデストラクタ呼び出し、それに続く配列削除を意味します。

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

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

30
EvilTeach

何かに応答するには Mehrdad 言いました:

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

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

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

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

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

これは、すべての主要なSTL実装で機能します。次の標準では、動作する必要があります(今日は正常に動作しますが)。

25
Frank Krueger

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

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

  1. コンパイル時に既知のサイズの静的。 --- std::array<T, N>
  2. 実行時に既知のサイズで動的にサイズ変更されることはありません。ここでの一般的な最適化は、配列をスタックに直接割り当てることができる場合です。 -利用不可。 C++ 14の後のC++ TSのdynarrayかもしれません。 CにはVLAがあります
  3. 実行時に動的でサイズ変更可能。 --- std::vector<T>

1。要素の数が固定されたプレーンな静的配列の場合、C++ 11でstd::array<T, N>を使用します。

2。固定サイズの配列は実行時に指定されますが、サイズは変更されません。C++ 14で議論されていますが、技術仕様に移動され、作成されました。最終的にC++ 14から。

For3。std::vector<T>は通常、ヒープ内のメモリを要求しますstd::vector<T, MyAlloc<T>>を使用してカスタムアロケーターで状況を改善できますが、これはパフォーマンスに影響を与える可能性があります。 T mytype[] = new MyType[n];と比較した場合の利点は、サイズを変更できることと、プレーン配列のようにポインターに減衰しないことです。

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

14
Germán Diago

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

6
John D. Cook

duliの貢献について

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

5
lalebarde

STLは高度に最適化されたライブラリです。実際、高いパフォーマンスが必要なゲームでSTLを使用することも提案されています。配列は、日々のタスクで使用されるエラーが多すぎます。今日のコンパイラも非常に賢く、STLで優れたコードを実際に生成できます。自分が何をしているのかわかっている場合、通常、STLは必要なパフォーマンスを提供できます。たとえば、ベクターを必要なサイズに初期化することにより(最初からわかっている場合)、基本的に配列のパフォーマンスを達成できます。ただし、配列が必要な場合があります。低レベルコード(アセンブリなど)または配列を必要とする古いライブラリとインターフェイスする場合、ベクターを使用できない場合があります。

5
Mehrdad Afshari

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

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

3
Juan

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

count引数(3番目の形式)をとるstd:vectorコンストラクターの c ++ spec には次のように記述されています。

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

3)Tのデフォルトで挿入されたcount個のインスタンスを使用してコンテナを構築します。コピーは作成されません。

複雑さ

2-3)線形カウント

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

すべての要素を初期化するためにstd :: vector <>を回避する方法はありますか?

3
bernie

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

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

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

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

2
Timo Geusch

次の簡単なテスト:

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

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

配列とベクトルには違いがなければなりません。テストはそう言っています...それを試してみてください、コードはそこにあります...

1
Hamed100101

インライン関数内のインライン関数内でベクトルにアクセスできるエッジのケースがあります。コンパイラがインライン化するものを超えて、関数呼び出しを強制します。それは心配する価値がないほどまれです-一般的に私は litb に同意します。

まだ誰もこれについて言及していないことに驚いています。パフォーマンスが問題であることが証明されてからベンチマークを行うまで、パフォーマンスについて心配しないでください。

1
Mark Ransom

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

1
Brian

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

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
duli

私は、主な懸念はパフォーマンスではなく、安全性だと主張します。配列を使用すると、多くの間違いを犯す可能性があります(たとえば、サイズ変更を検討してください)。

1

ベクトルを使用して多次元の動作を表現している場合、パフォーマンスが低下します。

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

要点は、サイズ情報を持つ各サブベクトルには少量のオーバーヘッドがあり、データのシリアル化は必ずしも必要ではないということです(多次元c配列の場合のように)。このシリアル化の欠如は、マイクロ最適化よりも大きな機会を提供します。多次元配列を使用している場合は、std :: vectorを拡張し、独自のget/set/resizeビット関数をロールするだけでよい場合があります。

1
Seph Reed

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

1
Greg Rogers

固定長の配列(たとえば、vのサイズを1000に固定した状態でint* v = new int[1000];std::vector<int> v(1000);)を想定すると、実際に重要な(または少なくとも同様のジレンマにあったときは私にとって重要な)パフォーマンスに関する唯一の考慮事項は要素へのアクセス。 STLのベクターコードを調べたところ、次のことがわかりました。

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

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

0
Subh_b