std :: vectorは単純な配列よりもはるかに遅いですか?


212

std::vector「配列として実装される」のは一般的な知恵だといつも思っていました。今日私は降りてそれをテストしました、そしてそうではないようです:

テスト結果は次のとおりです。

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

これは約3〜4倍遅くなります。「vector数ナノ秒は遅くなるかもしれない」コメントを正当化しません。

そして私が使用したコード:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

私はそれを間違っているのですか?それとも私はこのパフォーマンスの神話を破壊したのですか?

Visual Studio 2005でリリースモードを使用しています。


Visual C ++#define _SECURE_SCL 0軽減UseVector半分(4秒にそれをダウンさせ)。これは本当に巨大なIMOです。


23
デバッグモードの一部のバージョンのベクターは、配列の終わりを超えてアクセスできないことなどを確認するための追加の指示を追加します。実際のタイミングを取得するには、リリースモードでビルドし、最適化をオンにする必要があります。
マーティンヨーク

40
インターネットを介して聞いた主張を信じるのではなく、測定したのは良いことです。
Pが

51
ベクトル配列として実装されます。それは「従来の知恵」ではなく、真実です。vector汎用のサイズ変更可能な配列であることを発見しました。おめでとう。すべての汎用ツールと同様に、最適化されていない特殊な状況が発生する可能性があります。そのため、従来の知識ではa から始めvector必要に応じて代替案を検討します。
Dennis Zickefoose

37
笑、「汚れた食器を流しに投げ込む」と「汚れた食器を流しに投げ入れ、壊れなかったかどうかを確認する」の速度の違いは何ですか?
Imre L

9
少なくともVC2010では、主な違いは、malloc()がresize()より高速であることです。タイミングからメモリ割り当てを削除し、_ITERATOR_DEBUG_LEVEL == 0でコンパイルすると、結果は同じです。
Andreas Magnusson、2010

回答:


260

以下を使用します。

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArrayは2.196秒で
完了UseVectorは4.412秒で
完了UseVectorPushBack
は8.017秒で完了全体は14.626秒で完了

そのため、配列はベクトルの2倍高速です。

しかし、コードを詳細に調べたところ、これは予想されたものです。ベクトルを2回、配列を1回だけ実行すると、注:resize()ベクターを使用すると、メモリを割り当てるだけでなく、ベクター全体を実行して各メンバーのコンストラクターを呼び出すことになります。

ベクトルが各オブジェクトを1回だけ初期化するようにコードを少し再配置します。

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

同じタイミングをもう一度実行します。

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseVectorは2.216秒で完了しました

ベクトルのパフォーマンスは、アレイよりわずかに低くなっています。IMOこの違いは重要ではなく、テストに関連しない多くの事柄が原因である可能性があります。

またUseArrray()、コンストラクター/デストラクターのどちらも呼び出されないため、メソッドでPixelオブジェクトを正しく初期化/破棄していないことも考慮に入れます(これは、この単純なクラスの問題ではなく、少し複雑なもの(つまり、ポインターまたはメンバーの場合)ポインター付き)は問題を引き起こします。


48
@ kizzx2:のreserve()代わりに使用する必要がありますresize()。これにより、オブジェクトにスペースが割り当てられます(つまり、ベクターの容量が変更されます)が、オブジェクトは作成されません(つまり、ベクターのサイズは変更されません)。
James McNellis、2010

25
1 000 000 000の配列アクセスを実行しています。時間差は0.333秒です。または、アレイアクセスごとに0.000000000333の差。私のような2.33 GHzプロセッサが、アレイアクセスごとに0.7命令パイプラインステージであると仮定します。そのため、ベクターはアクセスごとに1つの追加命令を使用しているように見えます。
マーティンヨーク

3
@James McNellis:あなたは置き換えることはできませんresize()reserve()、これは、独自のサイズのベクトルの内部アイデアを調整していないため、その要素のため、後続の書き込みが技術的に「終わりを過ぎて書き込み」されているとUBを生成します。実際には、すべてのSTL実装がその点で「それ自体」動作しますが、ベクターのサイズを再同期するにはどうすればよいですか?ベクトルにデータを入力したresize() 後に呼び出しを試みるとこれらのすべての要素がデフォルトで作成された値で上書きされる可能性があります。
j_random_hacker

8
@j_random_hacker:私が言ったこととまったく同じではありませんか?reserveベクターのサイズではなく、容量のみを変更することは非常に明確であると思いました。
James McNellis、2010

7
わかりました。ベクトルメソッドには、多くの例外関連の問題がありました。/EHscコンパイルスイッチに追加すると、それがクリーンアップされ、assign()実際には配列に勝っています。わーい。
Pavel Minaev

55

すばらしい質問です。私はここに来て、ベクトルテストをすぐにスピードアップする簡単な修正を見つけることを期待していました。それは私が期待したようにうまくいかなかった!

最適化は役立ちますが、それだけでは不十分です。最適化を行っても、UseArrayとUseVectorの間に2倍のパフォーマンスの違いが見られます。興味深いことに、UseVectorは、最適化なしのUseVectorPushBackよりもかなり低速でした。

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

アイデア#1-mallocの代わりにnew []を使用する

オブジェクトが構築されるようmalloc()new[]、UseArrayに変更してみました。そして、個々のフィールド割り当てからPixelインスタンスの割り当てに変更します。ああ、内側のループ変数の名前をに変更しjます。

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

驚いたことに(私にとって)、これらの変更はどれもまったく違いがありませんでした。new[]デフォルトの変更ですべてのピクセルが構成されるわけではありません。gccは、を使用する場合、デフォルトのコンストラクター呼び出しを最適化できますが、を使用する場合は最適化できnew[]ないようvectorです。

アイデア#2-繰り返されるoperator []呼び出しを削除する

また、トリプルoperator[]ルックアップを削除して、への参照をキャッシュしようとしましたpixels[j]。これは実際にUseVectorを遅くしました!おっとっと。

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

アイデア#3-コンストラクターを削除する

コンストラクタを完全に削除するのはどうですか?次に、ベクトルが作成されたときに、gccはすべてのオブジェクトの構築を最適化できます。Pixelを次のように変更するとどうなりますか:

struct Pixel
{
    unsigned char r, g, b;
};

結果:約10%高速。それでもアレイよりも低速です。うーん。

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

アイデア#4-ループインデックスの代わりにイテレータを使用する

vector<Pixel>::iteratorループインデックスの代わりにを使用するのはどうですか?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

結果:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

いいえ、違いはありません。少なくともそれは遅くはありません。これは、Pixel&リファレンスを使用した#2と同様のパフォーマンスになると思いました。

結論

一部のスマートCookieがベクトルループを配列のループと同じくらい速くする方法を理解したとしても、これはのデフォルトの動作をうまく説明しませんstd::vector。すべてのC ++性を最適化し、STLコンテナーを生の配列と同じくらい高速にするのに十分なほどスマートであるコンパイラーにとっては、これだけです。

つまり、コンパイラーは、を使用するときに、何もしないデフォルトのコンストラクター呼び出しを最適化することができませんstd::vector。あなたがプレーンを使用する場合、new[]それはそれらをうまくうまく最適化します。しかし、ではありませんstd::vector。コードを書き直して、ここのマントラに直面して飛ぶコンストラクター呼び出しを排除できるとしても、「コンパイラーはあなたより賢いです。STLは普通のCと同じくらい高速です。心配しないでください。」


2
繰り返しますが、実際にコードを実行していただきありがとうございます。誰かが人気のある意見に異議を申し立てようとするとき、理由なしで打ちのめされることは時々簡単です。
kizzx2

3
「コンパイラーがすべてのC ++性を最適化し、STLコンテナーを生の配列と同じくらい高速にするのに十分なほどスマートであるには、これだけです。」いいコメント。私は、この「コンパイラーは賢い」は単なる神話であるという理論を持っています。C++の構文解析は非常に難しく、コンパイラーは単なるマシンです。
kizzx2

3
私は知らないよ。確かに、アレイテストの速度を落とすことはできましたが、ベクトルテストの速度は上げませんでした。上記で編集し、Pixelからコンストラクターを削除して単純な構造体にしましたが、それでも低速でした。これは、のような単純な型を使用する人にとっては悪いニュースですvector<int>
John Kugelman、2009

2
私は本当にあなたの答えを二度賛成できればいいのにと思います。(実際にはどれも機能しなかったとしても)私が想像もできなかった、試すための賢いアイデア!
kizzx2

9
C ++ の解析の複雑さ(めちゃくちゃ複雑です、はい)は、最適化の品質とは何の関係もないことに注意してください。後者は通常、解析結果がすでに非常に低レベルの表現に何度も変換されている段階で発生します。
Pavel Minaev

44

これは古くから人気のある質問です。

この時点で、多くのプログラマーがC ++ 11で作業します。また、C ++ 11では、記述されたとおりのOPのコードは、UseArrayまたはでも同等に高速に実行されますUseVector

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

基本的な問題は、Pixel構造が初期化されていない間、構築されstd::vector<T>::resize( size_t, T const&=T() )たデフォルトを使用してそれPixelコピーすることでした。コンパイラは、初期化されていないデータをコピーするように要求されていることに気づかなかったため、実際にコピーを実行しました。

C ++ 11では、std::vector<T>::resize2つのオーバーロードがあります。最初はstd::vector<T>::resize(size_t)、もう1 つはstd::vector<T>::resize(size_t, T const&)です。つまりresize、2番目の引数なしで呼び出すと、デフォルトの構成が単純になり、コンパイラはデフォルトの構成では何も実行されないことを認識できるほどスマートであるため、バッファのパスをスキップします。

(2つのオーバーロードが追加され、移動可能、構築可能、およびコピー不可能なタイプを処理します-初期化されていないデータで作業するときのパフォーマンスの向上はおまけです)。

push_backソリューションはまた、より遅いままので、それを遅くfencepostチェック、ないmallocバージョンを。

ライブの例(タイマーもに置き換えましたchrono::high_resolution_clock)。

通常は初期化を必要とする構造があるが、バッファーを拡張した後でそれを処理したい場合は、カスタムstd::vectorアロケーターを使用してこれを実行できます。次に、それをより通常のに移動したい場合はstd::vector、慎重に使用しallocator_traitsてオーバーライド==すると、それがうまくいかなくなると思いますが、確信が持てません。


また、どのように見るのは興味深いだろうemplace_back対しpush_back、ここ。
ダニエル

1
結果を再現できません。コードのコンパイルにはとclang++ -std=c++11 -O3UseArray completed in 2.02e-07 secondsありUseVector completed in 1.3026 secondsます。UseVectorEmplaceBack約バージョンも追加しました。2.5倍の速さUseVectorPushBack
ダニエル

1
@danielオッズは、オプティマイザが配列バージョンからすべてを削除したものです。マイクロベンチマークには常にリスクがあります。
Yakk-Adam Nevraumont 2015

4
はい、あなたは正しい、ちょうどアセンブリを見て(またはそれがないこと)。〜6448514xの違いを考えると、おそらくそれを考えるべきでした!ベクターバージョンでは同じ最適化を行うことができないのはなぜでしょうか。サイズ変更ではなく、次元を使用して構築した場合にそうなります。
ダニエル

34

公平を期すために、C ++実装をC実装と比較することはできません。mallocはオブジェクトを作成しません。生のメモリを割り当てるだけです。次に、コンストラクターを呼び出さずにそのメモリをオブジェクトとして扱うことは、C ++が貧弱です(おそらく無効です-言語弁護士に任せます)。

そうは言っても、mallocをnew Pixel[dimensions*dimensions]free toに変更するだけdelete [] pixelsでは、Pixelの単純な実装と大きな違いはありません。これが私の箱の結果です(E6600、64ビット):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

しかし、わずかな変更で、テーブルが変わります。

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

このようにコンパイルします:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

非常に異なる結果が得られます。

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

Pixelのインライン化されていないコンストラクターを使用すると、std :: vectorは未加工の配列に勝ります。

std :: vectorとstd:allocatorによる割り当ての複雑さは、単純な場合ほど効果的に最適化するには多すぎるようnew Pixel[n]です。ただし、問題は、ループの外側に移動してベクター/配列を一度作成するために、いくつかのテスト関数を微調整することにより、ベクターアクセスではなく割り当てにあることがわかります。

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

そして

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

これらの結果は今得られます:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

これから学べることは、std :: vectorはアクセス用のraw配列に相当しますが、vector / arrayを何度も作成および削除する必要がある場合、複雑なオブジェクトを作成すると、単純な配列を作成するよりも時間がかかります。要素のコンストラクターがインライン化されていない場合。これは驚くべきことではないと思います。


3
インラインコンストラクター、つまりコピーコンストラクターはまだあります。
Ben Voigt

26

これで試してください:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

配列とほぼ同じパフォーマンスが得られます。

事はvector、それが配列よりもはるかに一般的なツールだということです。つまり、それをどのように使用するを検討する必要があります。これはさまざまな方法で使用でき、配列にはない機能も提供します。そして、あなたがそれをあなたの目的のために「間違った」使用するならば、あなたは多くのオーバーヘッドを招きます、しかしそれを正しく使うならば、それは通常基本的にゼロオーバーヘッドのデータ構造です。この場合の問題は、ベクトルを個別に初期化し(すべての要素にデフォルトのctorが呼び出されるようにし)、次に各要素を正しい値で個別に上書きすることです。これは、配列で同じことを行う場合よりも、コンパイラーが最適化するのがはるかに困難です。これが、ベクターが正確にそれを行うためのコンストラクターを提供する理由です。NX

そして、それを使用すると、ベクトルは配列と同じくらい高速です。

だから、あなたはパフォーマンスの神話を破りました。しかし、ベクトルを最適に使用する場合にのみ真であることを示しましたが、これもかなり良い点です。:)

明るい面としては、最速であることが判明したのは、実際には最も単純な使用法です。私のコードスニペット(1行)をJohn Kugelmanの回答と比較してみてください。ヒープと調整および最適化のヒープが含まれていますが、パフォーマンスの違いはまだ完全には解消されていませんがvector、結局のところ巧妙に設計されていることは明らかです。アレイと同等の速度を得るために、フープをジャンプする必要はありません。それどころか、可能な限り単純なソリューションを使用する必要があります。


1
これが公正な比較であるかどうかはまだ疑問です。内側のループを取り除く場合、同等の配列は、単一のPixelオブジェクトを作成し、それを配列全体にブリットすることです。
John Kugelman、2010

1
を使用new[]すると、と同じデフォルトの構成が実行されますvector.resize()が、はるかに高速です。new[]+内側のループは+内側のループと同じ速度でなければなりvector.resize()ませんが、そうではありません。ほぼ2倍の速度です。
John Kugelman、2010

@ジョン:それは公正な比較です。元のコードでは、配列はmalloc何も初期化も構築もしないように割り当てられているため、実質的に私のようなシングルパスアルゴリズムです。vectorサンプルとです。そしてnew[]答えは明らかに両方とも2つのパスを必要とすることですが、このnew[]場合、コンパイラーは追加のオーバーヘッドを最適化することができますが、このvector場合はそうではありません。しかし、次善の場合に何が起こるのが興味深いのかはわかりません。パフォーマンスを気にするなら、そのようなコードを書く必要はありません。
2010

@ジョン:興味深いコメント。配列全体をブリットしたい場合は、やはり配列が最適なソリューションであると思いvector::resize()ます。役に立たないコンストラクターを呼び出す時間を無駄にせずに、メモリの偶然のチャンクを提供するように指示できないためです。
kizzx2

@ kizzx2:はい、いいえ。配列は通常、C ++でも初期化されます。Cでは、malloc初期化を実行しないwhichを使用しますが、それは非PODタイプのC ++では機能しません。そのため、一般的なケースでは、C ++配列も同様に悪くなります。おそらく問題は、このブリッティングを頻繁に実行する場合、同じ配列/ベクトルを再利用しないでしょうか?そして、それを行う場合、最初に「無駄なコンストラクタ」のコストを一度だけ支払うことになります。結局のところ、実際のブリッティングは同じくらい速いです。
2010

22

私が最初にコードを調べたとき、それは公平な比較ではありませんでした。私はあなたがリンゴをリンゴと比較しているのではないと思いました。そこで、すべてのテストでコンストラクタとデストラクタが呼び出されるようにしようと思いました。次に比較します。

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

私の考えは、このセットアップでは、それらはまったく同じになるはずだということでした。結局、私は間違っていました。

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

では、なぜこの30%のパフォーマンス低下が発生したのでしょうか。STLはヘッダーにすべてを持っているので、コンパイラーが必要なすべてを理解できるはずです。

私の考えは、ループがすべての値をデフォルトのコンストラクターに初期化する方法にあると考えていました。だから私はテストを行いました:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

結果は私が疑ったとおりでした:

Default Constructed: 1
Copy Constructed: 300

これが明らかにスローダウンの原因であり、ベクターがコピーコンストラクターを使用して、デフォルトで構築されたオブジェクトから要素を初期化するという事実です。

これは、ベクトルの構築中に次の疑似操作順序が発生することを意味します。

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

これは、コンパイラーによって作成された暗黙的なコピーコンストラクターにより、次のように展開されます。

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

したがって、デフォルトPixelは初期化されていないままですが、残りはデフォルトPixel初期化されていない状態で初期化されます値でます。

との代替状況と比較して New[]/Delete[]

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

それらはすべて初期化されていない値に任されており、シーケンスの二重反復はありません。

この情報を武器に、どのようにテストできますか?暗黙のコピーコンストラクタを上書きしてみましょう。

Pixel(const Pixel&) {}

そして結果は?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

要約すると、非常に頻繁に何百ものベクトルを作成している場合: アルゴリズムを再考する

いずれにせよ、STLの実装は、なんらかの理由で遅くなることはありません。単に要求されたとおりのことを行うだけです。あなたがよく知っていることを願っています。


3
私たち(あなたと私とここに他のスマート人)が持っていた楽しいから判断すると、STLのimplemenationの「希望」は確かにかなり厳しいものです:Pは基本的に、我々はそれが私が読んできたいと考えていることを誇張すると結論付けることができ、分析のすべてのソースをコード。とにかく:P
kizzx2

1
かっこいい!VS 2013では、これによりベクトルが配列よりも高速になりました。パフォーマンスが重要なシステムの場合、STLを効果的に使用するには、STLをたくさんテストする必要があるようです。
rozina 2013年

7

チェックされたイテレータを無効にして、リリースモードでビルドしてみてください。パフォーマンスの違いはあまり見られません。


1
試しました#define _SECURE_SCL 0。それはUseVectorどこかで約4秒(gcc以下と同様)になりますが、それでも2倍遅いです。
kizzx2

これはほぼ間違いなく原因です。マイクロソフトでは、デバッグとリリースの両方で、デフォルトでイテレータのデバッグを有効にしています。2003年から2008年にアップグレードした後、これが大幅な速度低下の根本的な原因であることがわかりました。間違いなく、ビジュアルスタジオの最も悪質な問題の1つです。
Doug T.

2
@ kizzx2無効にする別のマクロがあります:HAS_ITERATOR_DEBUGGINGまたはそのようなもの。
Doug T.

@Martinと私の回答が示すように、gccはで最適化されていても同じパターンを示します-O3
John Kugelman、2009

1
@Doug:ドキュメントを見ると、_HAS_ITERATOR_DEBUGGINGリリースビルドでは無効になっていると思います:msdn.microsoft.com/en-us/library/aa985939
VS.80).aspx

4

vector<T>(n)デフォルトでは、GNUのSTL(およびその他)はプロトタイプオブジェクトを構築しますT() -コンパイラーは空のコンストラクターを最適化します-しかし、オブジェクト用に予約されているメモリアドレスにあるガベージのコピーがSTLによって取得されます__uninitialized_fill_n_aux。そのオブジェクトのコピーをベクターのデフォルト値として取り込むループ。したがって、「私の」STLはループ構築ではなく、構築してからループ/コピーを構築します。これは直感に反しますが、この点に関する最近のスタックオーバーフローの質問にコメントしたので、覚えておく必要がありました。構成/コピーは、参照カウントされたオブジェクトなどに対してより効率的です。

そう:

vector<T> x(n);

または

vector<T> x;
x.resize(n);

は-多くのSTL実装で-以下のようなものです:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

現在の世代のコンパイラオプティマイザは、tempが初期化されていないガベージであり、ループとデフォルトのコピーコンストラクタの呼び出しを最適化できないという洞察からは機能しないようです。上記を書いているプログラマーは、ガベージ(通常、 '同一' /演算子== memcmp / operator =などが適用されます)。コンパイラーは、std :: vector <>のより大きなコンテキストや、この最適化が安全であることを示唆するデータの後の使用法について、追加の洞察を期待することはできません。

これは、より明確で直接的な実装とは対照的です。

for (int i = 0; i < n; ++i)
    x[i] = T();

コンパイラが最適化することを期待できます。

ベクトルの動作のこの側面の正当化についてもう少し明確にするには、以下を考慮してください。

std::vector<big_reference_counted_object> x(10000);

明らかに、同じデータを参照する10000個のオブジェクトと10000個の独立したオブジェクトを作成する場合の大きな違いです。カジュアルなC ++ユーザーが誤って非常に高価なことをしないように保護することの利点は、最適化が難しいコピー構築の非常に小さな実際のコストを上回るという合理的な議論があります。

元の回答(参照用/コメントの意味):チャンスはありません。ベクトルは、少なくともスペースを慎重に予約した場合、配列と同じくらい高速です。...


6
私はこの答えが誰にとっても少しでも役に立つと正当化することはできません。私は2度投票することを望みます。
kizzx2

-1、kizzx2への私のサポートがあります。ベクトルは、それが提供する追加機能、宇宙の法則、すべてに価格があるため、配列ほど速くはなりません!
YeenFei

あなたは見逃している、トニー…それは人工的なベンチマークの例ですが、それが意図することを証明しています。
Potatoswatter

バラは緑、スミレはオレンジ、反対票は苦いですが、答えはそれらを物乞いします。
Pavel Minaev

3

マーティンヨークの答えは、カーペットの下で初期化の問題を解決する試みのように思われるので、私を困らせます。しかし、パフォーマンスの問題の原因として、冗長なデフォルトの構成を特定するのは正しいことです。

[編集:マーティンの答えはもはやデフォルトのコンストラクターを変更することを提案していません。]

当面の問題については、vector<Pixel>代わりに2パラメータバージョンのctorを呼び出すことができます。

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

これは、一般的なケースである定数値で初期化する場合に機能します。しかし、より一般的な問題は、定数値よりも複雑なもので効率的に初期化するにどうすればよいですか?

back_insert_iteratorこれには、反復子アダプターであるを使用できます。以下はintsのベクトルの例ですが、一般的な考え方はPixels でも同様に機能します。

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

または、copy()またはのtransform()代わりにを使用することもできますgenerate_n()

欠点は、初期値を構築するためのロジックを別のクラスに移動する必要があることです。これは、その場所に配置するよりも便利ではありません(ただし、C ++ 1xのラムダを使用すると、はるかに優れたものになります)。また、これはまだmalloc()ベースの非STLバージョンほど高速ではないことを期待していますが、各要素に対して1つの構築しか行わないため、近いものになると期待しています。


2

ベクトルのものは、さらにPixelコンストラクターを呼び出しています。

それぞれがタイミングを合わせている約100万の俳優の実行を引き起こしています。

編集:それから外側の1 ... 1000ループがあるので、10億の俳優が呼ばれるようにしてください!

編集2:UseArrayケースの逆アセンブリを見るのは興味深いでしょう。オプティマイザはCPUを燃やす以外に効果がないため、全体を最適化することができます。


あなたは正しいですが、問題は、これらの無意味な俳優の呼び出しをオフにするにはどうすればよいですか?STL以外のアプローチでは簡単ですが、STLの方法では難しく/醜いです。
j_random_hacker

1

push_backベクトルのメソッドの仕組みは次のとおりです。

  1. ベクトルは、初期化時にX分のスペースを割り当てます。
  2. 後述するように、現在の基本配列にアイテムのスペースがあるかどうかをチェックします。
  3. push_back呼び出しでアイテムのコピーを作成します。

push_backXアイテムを呼び出した後:

  1. ベクトルは、kXの量のスペースを2番目の配列に再割り当てします。
  2. 最初の配列のエントリを2番目の配列にコピーします。
  3. 最初の配列を破棄します。
  4. これで、kXエントリに達するまで、2番目の配列をストレージとして使用します。

繰り返す。そうでない場合reservingスペース、確実に遅くなります。それ以上に、アイテムをコピーするのにコストがかかる場合、そのような 'push_back'はあなたを生きたまま食べるでしょう。

vector対配列の事、私は他の人に同意する必要がありますするつもりです。リリースで実行し、最適化をオンにし、さらにいくつかのフラグを設定して、Microsoftの友好的な人々がそれを#@%$ ^しないようにします。

もう1つ、サイズを変更する必要がない場合は、Boost.Arrayを使用します。


それが逐語的に投稿されたとき、人々はたくさんのコードを読むのが好きではないことを理解しています。しかし、私は必要reserveなように使用しました。
kizzx2

すみません、見逃しました。私がそこに置いた他に何も役に立ちませんでしたか?
ウィーティー

push_back一定の時間を償却しました。O(N)プロセスを説明しているようです。(手順1と3は完全にずれているように見えます。)push_backOPが遅くなるのは、再割り当てが発生する必要があるかどうかを確認するための範囲チェック、ポインタの更新、配置内のNULLに対するチェックnew、および通常は溺れるその他の小さなことです。プログラムの実際の作業。
Potatoswatter

すべてreserveのでチェック(再割り当てが必要かどうか)を行う必要があるので、それでも遅くなりますpush_back
Pavel Minaev

すべての良い点。私が説明していることはO(N)プロセスのように聞こえますが、そうではありません。私が知っているほとんどの人は、a vectorがどのようにリサイズ機能を実行するかを理解していません。それは単なる「魔法」です。ここで、もう少しはっきりさせておきます。
ウィーティー

1

一部のプロファイラーデータ(ピクセルは32ビットに揃えられています):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

ブラ

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

allocator

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

アレイ

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

オーバーヘッドのほとんどはコピーコンストラクタにあります。例えば、

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

アレイと同じパフォーマンスです。


2
残念ながら、あなたが与えた「解決策」の後、pixels.size()壊れます。
kizzx2

1
これは間違っています。reserveを呼び出して要素を使用することはできません。アイテムを追加するには、push_backを使用する必要があります
paulm

1

私のラップトップはLenova G770(4 GB RAM)です。

OSはWindows 7 64ビット(ラップトップ搭載)

コンパイラはMinGW 4.6.1です。

IDEはCode :: Blocksです。

最初の投稿のソースコードをテストします。

結果

O2最適化

UseArrayは2.841秒で完了しました

UseVectorは2.548秒で完了しました

UseVectorPushBackは11.95秒で完了しました

すべてが17.342秒で完了しました

システム一時停止

O3最適化

UseArrayは1.452秒で完了しました

UseVectorは2.514秒で完了しました

UseVectorPushBackは12.967秒で完了しました

すべてが16.937秒で完了しました

O3最適化では、ベクトルのパフォーマンスが低下するようです。

ループを次のように変更した場合

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

O2とO3での配列とベクトルの速度はほぼ同じです。


mallocをnewに変更しても、O3での最初のテストケースでは、ベクトルのパフォーマンスは配列よりも低速ですが、割り当て値を(255、0、0)から(i、i、i)に変更すると、ベクトルと配列はO2とO3でほとんど同じですが、かなり奇妙です
StereoMatching

削除して自由に変更するのを忘れました。削除して自由に変更した後、O3でのベクトルと配列のパフォーマンスは同じになりました。アロケータが主な理由のように見えますか?
StereoMatching

1

より良いベンチマーク(と思います...)、最適化によるコンパイラはコードを変更する可能性があります。割り当てられたベクトル/配列の結果はどこでも使用されないためです 結果:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

コンパイラ:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

CPU:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

そしてコード:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

私はしばらくの間、やりたかったいくつかの広範なテストを行いました。これも共有するかもしれません。

これは、Windows 8.1およびUbuntu 16.04のデュアルブートマシンi7-3770、16GB Ram、x86_64です。詳細と結論、以下の備考。MSVS 2017とg ++の両方をテストしました(WindowsとLinuxの両方で)。

テストプログラム

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

結果

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

ノート

  • 平均10回の実行によって組み立てられます。
  • 私も最初はstd::sort()あまりにもテストしました(コメントアウトされているのがわかります)が、相対的な有意差がないため、後でそれらを削除しました。

私の結論と備考

  • グローバルなc-style配列がヒープのc-style配列とほぼ同じ時間かかることに注意してください
  • すべてのテストの中で、私は顕著な安定性に気づきました std::array、連続した実行の間のの時間変動に特にstd ::データ構造が比較し変動しました
  • O3最適化では、注目すべき時間差は示されませんでした
  • Windows cl(-O2なし)およびg ++(Win / Linuxで-O2なし、-march = nativeなし)で最適化を削除すると、時間が大幅に増加します。特にstd :: data構造体の場合。MSVSでは全体的にg ++よりも時間がかかりstd::arrayますが、Windowsではcスタイルの配列は最適化なしで高速です
  • g ++は、マイクロソフトのコンパイラよりも高速なコードを生成します(どうやらWindows上でも高速に実行されます)。

評決

もちろん、これは最適化されたビルドのコードです。そして質問はstd::vectorそれについてだったので、はい、それは!プレーン配列よりも遅い(最適化/非最適化)。しかし、ベンチマークを行っているときは、当然、最適化されたコードを生成する必要があります。

私のためのショーのスターはされていますがstd::array


0

適切なオプションを使用すると、ベクトルと配列は同じasm生成できます。これらの場合、どちらの方法でも同じ実行可能ファイルを取得できるため、もちろん速度は同じです。


1
この場合、それらは同じアセンブリを生成しないようです。特に、ベクトルを使用したコンストラクターの呼び出しを抑制する方法はないようです。あなたはその問題の回答をここで参照できます(長い説明ですが、提供したリンクの単純なテストケース以外の場合にパフォーマンスの違いがある理由を説明しています)(実際には、方法があるようです- -カスタムSTLアロケーターを作成する(推奨)個人的には、mallocを使用するよりも不必要に多くの作業が必要です)
kizzx2

1
@ kizzx2:構築されていないオブジェクトを使用するためにこのような長さにする必要があることは、99%のエラー(私は大幅に過小評価している可能性がある)であるため、良いことです。私は他の答えを読みましたが、私はあなたの特定の状況に対処していないことを理解しています(必要はありません、他の答えは正しいです)が、ベクトルと配列がまったく同じように動作する方法のこの例を提供したいと思います。

@ロジャー:それは素晴らしい!リンクをありがとう
kizzx2

0

ちなみに、vectorを使用したクラスでの表示が遅くなるのは、intなどの標準型でも発生します。ここにマルチスレッドコードがあります:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

コードの動作は、ベクトルのインスタンス化がコードの最も長い部分であることを示しています。ボトルネックを通り抜けたら。残りのコードは非常に高速に実行されます。これは、実行しているスレッドの数に関係なく当てはまります。

ちなみに、非常に多くのインクルードは無視してください。このコードを使用してプロジェクトのテストを行っているため、インクルードの数は増え続けています。


0

ベクター(およびsmart_ptr)は、生の配列(および生のポインター)の上に追加される薄層にすぎないことを述べておきます。そして実際には、連続メモリ内のベクトルのアクセス時間は配列よりも高速です。次のコードは、ベクトルと配列の初期化とアクセスの結果を示しています。

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

出力は次のとおりです。

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

したがって、適切に使用すれば、速度はほぼ同じになります。(他の人がreserve()またはresize()を使用して言及したように)。


0

まあ、vector :: resize()は(mallocによる)単純なメモリ割り当てよりもはるかに多くの処理を行うからです。

コピーコンストラクターにブレークポイントを設定してみて(ブレークポイントを設定できるように定義してください)、処理時間が長くなります。


0

私はC ++の専門家ではないと言う必要があります。しかし、いくつかの実験結果を追加するには:

コンパイル:gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

機械:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

OS:

2.6.32-642.13.1.el6.x86_64

出力:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

ここで私が奇妙に感じる唯一のことは、「UseConstructor」と比較した「UseFillConstructor」のパフォーマンスです。

コード:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

したがって、追加の「値」を指定するとパフォーマンスが大幅に低下します。これは、コピーコンストラクターへの複数の呼び出しが原因であると思います。だが...

コンパイル:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

出力:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

したがって、この場合、gccの最適化は非常に重要ですが、値がデフォルトとして提供されている場合はあまり役に立ちません。これは、実際には私の授業料に反対です。うまくいけば、新しいプログラマーがどのベクトル初期化フォーマットを選択するかを支援します。


0

コンパイラのフラグに依存しているようです。ベンチマークコードは次のとおりです。

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

最適化フラグが異なると、回答も異なります。

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

実際の結果はさまざまですが、これは私のマシンでは非常に一般的です。


0

私の経験では、時々、たまに、vector<int>何倍も遅い場合がありint[]ます。心に留めておくべきことの1つは、ベクトルのベクトルは非常に異なるということint[][]です。要素はおそらくメモリ内で隣接していないためです。これは、メインのベクトル内で異なるベクトルのサイズを変更できることを意味しますが、CPUはの場合と同様に要素をキャッシュできない場合がありint[][]ます。

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