2


0

クラスと構造体の効率的なプッシュバック

同僚が2番目のスニペットを頻繁に実行するのを見てきました。 どうしてこれなの? print文を追加してctorとdtorを追跡しようとしましたが、どちらも同じように見えます。

    std::vector vecClass1;
    ClassTest ct1;
    ct1.blah = blah // set some stuff
    ...
    vecClass1.push_back(ct1);
    std::vector vecClass2;
    vecClass2.push_back(ClassTest());
    ClassTest& ct2 = vecClass2.back();
    ct2.blah = blah // set some stuff
    ...

PS. タイトルが誤解を招く場合は申し訳ありません。

編集:

まず、ご回答ありがとうございました。

`std

move`を使用して小さなアプリケーションを作成しました。 おそらく私が何か間違ったことをしたので、結果は私にとって驚くべきものです…​ 「高速」パスのパフォーマンスが大幅に向上している理由を誰かが説明してください。

#include
#include
#include
#include

const std::size_t SIZE = 10*100*100*100;
//const std::size_t SIZE = 1;
const bool log = (SIZE == 1);

struct SomeType {
    std::string who;
    std::string bio;
    SomeType() {
        if (log) std::cout << "SomeType()" << std::endl;
    }
    SomeType(const SomeType& other) {
        if (log) std::cout << "SomeType(const SomeType&)" << std::endl;
        //this->who.swap(other.who);
        //this->bio.swap(other.bio);
        this->who = other.who;
        this->bio = other.bio;
    }
    SomeType& operator=(SomeType& other) {
        if (log) std::cout << "SomeType::operator=()" << std::endl;
        this->who.swap(other.who);
        this->bio.swap(other.bio);
        return *this;
    }
    ~SomeType() {
        if (log) std::cout << "~SomeType()" << std::endl;
    }
    void swap(SomeType& other) {
        if (log) std::cout << "Swapping" << std::endl;
        this->who.swap(other.who);
        this->bio.swap(other.bio);
    }
        // move semantics
    SomeType(SomeType&& other) :
          who(std::move(other.who))
        , bio(std::move(other.bio)) {
        if (log) std::cout << "SomeType(SomeType&&)" << std::endl;
    }
    SomeType& operator=(SomeType&& other) {
        if (log) std::cout << "SomeType::operator=(SomeType&&)" << std::endl;
        this->who = std::move(other.who);
        this->bio = std::move(other.bio);
        return *this;
    }
};

int main(int argc, char** argv) {

    {
        boost::progress_timer time_taken;
        std::vector store;
        std::cout << "Timing \"slow\" path" << std::endl;
        for (std::size_t i = 0; i < SIZE; ++i) {
            SomeType some;
            some.who = "bruce banner the hulk";
            some.bio = "you do not want to see me angry";
            //store.push_back(SomeType());
            //store.back().swap(some);
            store.push_back(std::move(some));
        }
    }
    {
        boost::progress_timer time_taken;
        std::vector store;
        std::cout << "Timing \"fast\" path" << std::endl;
        for (std::size_t i = 0; i < SIZE; ++i) {
            store.push_back(SomeType());
            SomeType& some = store.back();
            some.who = "bruce banner the hulk";
            some.bio = "you do not want to see me angry";
        }
    }
    return 0;
}

出力:

[email protected]:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x
[email protected]:~/Desktop/perf_test$ ./a.out
Timing "slow" path
3.36 s

Timing "fast" path
3.08 s

5 Answer


7


「何かを設定」した後のオブジェクトのコピーのコストが以前よりも高い場合、ベクトルにオブジェクトを挿入したときに発生するコピーは、「ものを設定」する前よりも後のオブジェクトを挿入した方が安価です。

ただし、実際には、ベクトル内のオブジェクトは時折コピーされることが予想されるため、これはおそらくあまり最適化されていません。


3


ClassTestはコピーするのに費用がかかるため、同僚のスニペットが賢明であると認める場合は、次のようにします。

using std::swap;

std::vector vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(ClassTest());
swap(ct1, vecClass1.back());

私はそれがより明確だと思います、そしてそれはより例外的に安全かもしれません。 `…​`コードはおそらくリソースを割り当てるため、例外をスローする可能性があります(または、完全に構築された `ClassTest`がコピーするのに非常に費用がかかるのはなぜですか?)。 したがって、ベクトルが実際に関数に対してローカルでない限り、そのコードの実行中にベクトルを半分構築するのは良い考えだとは思いません。

もちろん、これは ClassTest`がデフォルトの swap`実装のみを持っている場合はさらに高価ですが、 ClassTest`が効率的な swap`を持たない場合、コピーするのにコストのかかるビジネスはありません。 そのため、このトリックは、未知のテンプレートパラメータタイプではなく、フレンドリーであることがわかっているクラスでのみ使用する必要があります。

Geneが言うように、C ++ 0x機能があれば、 `std

move`の方が優れています。

ただし、ClassTestのコピーに費用がかかることが心配な場合は、ベクターの再配置は恐ろしい見通しです。 したがって、次のいずれかを行う必要があります。

  • 何かを追加する前に十分なスペースを確保し、

  • 「ベクター」の代わりに「deque」を使用します。


2


2番目のバージョンは、一時的な移動から恩恵を受けます。 最初のバージョンは、一時ベクトルをコピーしています。 したがって、2番目の方法は潜在的に高速です。 また、2番目のバージョンではピークメモリ要件が小さくなる可能性があります。1番目のバージョンでは、2つのオブジェクトを1つの一時ファイルとそのコピーを作成してから、一時ファイルのみを削除します。 一時ファイルを明示的に移動することにより、最初のバージョンを改善できます。

std::vector vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(std::move(ct1));


1


おそらく、同僚に「正確に」なぜなのかを尋ねる必要がありますが、推測することはできます。 ジェームズが指摘したように、オブジェクトが作成されてからコピーするのがより高価であれば、少し効率的かもしれません。

どちらのバージョンにも利点があります。

同僚のスニペットが気に入っています:どちらの場合も2つのオブジェクトがありますが、2番目のバージョンではごく短時間しか共存しません。 編集に使用できるオブジェクトは1つのみです。これにより、「push_back」の後に「ct1」を編集する潜在的なエラーを回避できます。

私はあなたのスニペットが好きです:2番目のオブジェクトを追加するために push_back`を呼び出す_potentially_は参照 ct2`を無効にし、未定義の動作のリスクを引き起こします。 最初のスニペットにはこのリスクはありません。


0


それらは同一です(私が見る限り)。 たぶん彼または彼女は慣用的な習慣としてそれをします。