私たちのC++コースでは、新しいプロジェクトでC++配列を使用しないことを提案しています。私の知る限り、Stroustroup自身は配列を使用しないことを提案しています。しかし、パフォーマンスに大きな違いはありますか?
20 に答える
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.
注:ユーザー定義のコンストラクターを使用せずに配列を割り当て、非クラス オブジェクト (plain など) またはクラスを割り当てnew
、要素を最初に初期化したくない場合は、-allocated 配列を使用すると、すべての要素が次のように初期化されるため、パフォーマンス上の利点があります。構築時のデフォルト値 (たとえば、int の場合は 0) (@bernie のおかげで思い出させてくれました)。int
new
std::vector
マイクロオプティマイザーの人々のための前文
覚えて:
「プログラマーは、プログラムの重要でない部分の速度について考えたり心配したりするのに膨大な時間を浪費します。これらの効率化の試みは、デバッグとメンテナンスを考慮すると、実際には大きな悪影響を及ぼします。小さな効率性については忘れてください。 97%の確率で、時期尚早の最適化はすべての悪の根源です。それでも、その重要な3%でチャンスを逃してはなりません。」
(完全な引用のための変態に感謝します)
ベクトル(またはその他)の代わりにC配列を使用しないでください。それは、低レベルであると想定されているため、より高速であると信じているからです。あなたは間違っているでしょう。
デフォルトのベクトル(またはニーズに合わせた安全なコンテナー)を使用し、プロファイラーが問題であると言った場合は、より優れたアルゴリズムを使用するか、コンテナーを変更することで、最適化できるかどうかを確認します。
そうは言っても、元の質問に戻ることができます。
静的/動的アレイ?
C ++配列クラスは、低レベルのC配列よりも動作が優れています。これは、C ++配列が自身について多くのことを知っており、C配列ではできない質問に答えることができるためです。彼らは自分たちの後できれいにすることができます。さらに重要なことに、これらは通常、テンプレートやインライン化を使用して記述されます。つまり、デバッグで多くのコードに表示されるものは、リリースビルドで生成されたコードがほとんどまたはまったくないため、組み込みの安全性の低い競合との違いはありません。
全体として、それは2つのカテゴリに分類されます。
動的配列
malloc-ed / new-ed配列へのポインターを使用すると、せいぜいstd :: vectorバージョンと同じくらい高速になり、安全性が大幅に低下します(litbの投稿を参照)。
したがって、std::vectorを使用します。
静的配列
静的配列の使用はせいぜい次のようになります。
- std::arrayバージョンと同じくらい速い
- 安全性が大幅に低下します。
したがって、std::arrayを使用します。
初期化されていないメモリ
bernieが答えたように、rawバッファーの代わりに使用すると、構築時にバッファーが初期化されるのに対し、置換するコードは初期化されvector
ないため、目に見えるコストが発生することがあります。vector
この場合、またはのunique_ptr
代わりに使用して処理できvector
ます。コードラインで例外がない場合は、実際にbuffer_owner
そのメモリを所有するクラスを作成し、次のようなメモリに簡単かつ安全にアクセスできるようにします。サイズ変更(?を使用)などのボーナスrealloc
、または必要なものは何でも。
ベクトルは内部の配列です。性能は同じです。
パフォーマンスの問題が発生する可能性のある場所の 1 つは、ベクターのサイズを最初から正しく設定していないことです。
ベクターがいっぱいになると、それ自体のサイズが変更されます。これは、新しい配列の割り当て、それに続く n 個のコピー コンストラクター、約 n 個のデストラクタの呼び出し、それに続く配列の削除を意味する可能性があります。
コンストラクト/デストラクトが高価な場合は、ベクターを最初から正しいサイズにする方がはるかに優れています。
これを示す簡単な方法があります。いつ構築/破棄/コピー/割り当てされたかを示す単純なクラスを作成します。これらのもののベクトルを作成し、それらをベクトルのバックエンドにプッシュし始めます。ベクターがいっぱいになると、ベクターのサイズが変更されるため、一連のアクティビティが発生します。次に、予想される要素数に合わせたサイズのベクトルで再試行します。違いがわかります。
Mehrdadが言ったことに答えるために:
ただし、配列が必要な場合もあります。低レベル コード (アセンブリなど) や配列を必要とする古いライブラリとのインターフェイスでは、ベクトルを使用できない場合があります。
まったくそうではありません。以下を使用すると、ベクトルは配列/ポインターにうまく分解されます。
vector<double> vector;
vector.push_back(42);
double *array = &(*vector.begin());
// pass the array to whatever low-level code you have
これは、すべての主要な STL 実装で機能します。次の標準では、動作することが要求されます (現在は問題なく動作していますが)。
STLで行きます。パフォーマンスのペナルティはありません。アルゴリズムは非常に効率的で、私たちのほとんどが考えないような詳細をうまく処理してくれます。
STLは高度に最適化されたライブラリです。実際、高性能が必要となる可能性のあるゲームでSTLを使用することも提案されています。配列はエラーが発生しやすいため、日常のタスクで使用することはできません。今日のコンパイラも非常にスマートで、STLを使用して優れたコードを生成できます。自分が何をしているのかがわかっている場合、STLは通常必要なパフォーマンスを提供できます。たとえば、ベクトルを必要なサイズに初期化することで(最初から知っている場合)、基本的に配列のパフォーマンスを実現できます。ただし、それでもアレイが必要な場合があります。低レベルのコード(つまり、アセンブリ)または配列を必要とする古いライブラリとインターフェイスする場合、ベクトルを使用できない場合があります。
私自身の測定によるduliの貢献について。
結論として、整数の配列は整数のベクトルよりも高速です (私の例では 5 倍)。ただし、配列とベクトルは、より複雑な/整列されていないデータの場合、ほぼ同じ速度です。
ソフトウェアをデバッグ モードでコンパイルする場合、多くのコンパイラはベクトルのアクセサ関数をインライン化しません。これにより、パフォーマンスが問題となる状況で stl ベクトルの実装が大幅に遅くなります。また、デバッガーで割り当てられたメモリの量を確認できるため、コードのデバッグが容易になります。
最適化モードでは、stl ベクトルが配列の効率に近づくことを期待します。これは、ベクター メソッドの多くがインライン化されたためです。
この 2 つのパフォーマンスの違いは、実装に大きく依存します。不適切に実装された std::vector を最適な配列の実装と比較すると、配列が優先されますが、逆にすると、ベクトルが優先されます...
リンゴとリンゴを比較する限り (配列とベクトルの両方に要素数が固定されているか、両方が動的にサイズ変更される)、STL コーディングの実践に従っている限り、パフォーマンスの違いは無視できると思います。標準 C++ コンテナーを使用すると、標準 C++ ライブラリの一部である事前に作成されたアルゴリズムも利用できることを忘れないでください。それらのほとんどは、自分で構築した同じアルゴリズムの平均的な実装よりも優れたパフォーマンスを発揮する可能性があります。 .
そうは言っても、適切なデバッグモードを備えたほとんどのSTL実装は、標準コンテナを操作するときに人々が犯した典型的な間違いを少なくとも強調/キャッチできるため、デバッグSTLを使用したデバッグシナリオではベクトルが勝ちます。
ああ、配列とベクトルは同じメモリ レイアウトを共有することを忘れないでください。これにより、基本的な配列を必要とする従来の C または C++ コードにデータを渡すためにベクトルを使用できます。ただし、そのシナリオではほとんどの賭けが外れていることに注意してください。また、生のメモリを扱っていることになります。
インライン関数内のインライン関数内でベクトルにアクセスし、コンパイラがインライン化する範囲を超えて、関数呼び出しを強制するエッジ ケースがあるかもしれません。それは非常にまれであるため、心配する価値はありません。一般的に、私はlitbに同意します。
これについてまだ誰も言及していないことに驚いています。問題であることが証明されるまでパフォーマンスについて心配する必要はありません。
サイズを動的に調整する必要がない場合は、容量を節約するためのメモリ オーバーヘッドがあります (1 つのポインター/size_t)。それでおしまい。
配列の方がベクトルよりも優れている場合があります。オブジェクトの固定長セットを常に操作している場合は、配列の方が適しています。次のコード スニペットを検討してください。
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];}
};
内部ループで毎回「new」のオーバーヘッドを回避しているため、配列バージョンの main() の方が高速になります。
(このコードは私が comp.lang.c++ に投稿したものです)。
主な関心事はパフォーマンスではなく、安全性であると私は主張します。配列では多くの間違いを犯す可能性があります (たとえば、サイズ変更を検討してください)。ベクトルを使用すると、多くの手間が省けます。
ベクトルには配列のサイズが含まれているため、配列よりもわずかに多くのメモリを使用します。また、プログラムのハード ディスク サイズが増加し、おそらくプログラムのメモリ フットプリントも増加します。これらの増加はわずかですが、組み込みシステムを使用している場合は問題になる可能性があります. ただし、これらの違いが問題となるほとんどの場所は、C++ ではなく C を使用する場所です。
次の簡単なテスト:
「基本的なインデックス付け、逆参照、およびベクトルと配列/ポインターのインクリメント操作のために生成されたアセンブリ コードの比較」の結論と矛盾します。
配列とベクトルには違いがあるはずです。テストはそう言っています...試してみてください、コードはそこにあります...