2

SOのコピーコンストラクター/代入演算子についてはすでにかなりの数の質問がありますが、私の問題に合う答えは見つかりませんでした。

私は次のようなクラスを持っています

class Foo
{
   // ...
private:
   std::vector<int> vec1;
   std::vector<int> vec2;
   boost::bimap<unsigned int, unsigned int> bimap;
   // And a couple more
};

現在、(プロファイルデータに基づいて)かなり過剰なコピーが行われているようです。それで、私の質問は、これに最もよく取り組む方法ですか?

カスタムコピーコンストラクタ/代入演算子を実装してスワップを使用する必要がありますか?または、独自のスワップメソッドを定義し、割り当ての代わりにそれを(適切な場合)使用する必要がありますか?

私はC++の専門家ではないので、この状況を適切に処理する方法を示す例を高く評価します。

更新:私はひどく明確ではなかったようです..説明しようと思います。このプログラムは基本的にオンザフライの幅優先探索プログラムであり、実行されるステップごとに、ステップ(Fooクラス)に関するメタデータを格納する必要があります。問題は、(通常は)指数関数的なステップがあることです。したがって、これらのオブジェクトを多数保存する必要があることを想像できます。私が知る限り、常に(const)参照を渡します。グラフのノードから後継を計算するたびに、作成して保存する必要があります。 1つのFooオブジェクト(ただし、この後続の処理では、一部のデータメンバーがこの1つのfooに追加されます)。

私のプロファイルデータは、おおよそ次のようなものを示しています(このマシンには実際の番号がありません):

SearchStrategy::Search    13s
FooStore::Save            10s

したがって、グラフを検索するのと同じくらい多くの時間をこのメタデータの保存に費やしていることがわかります。ああ、FooStoreはに保存Foogoogle::sparse_hash_map<long long, Foo, boost::hash<long long> >ます。

コンパイラはg++4.4またはg++4.5です(私は開発マシンにいないので、現時点では確認できません)。

UPDATE 2構築後、メンバーの一部を次のようなFooインスタンスに割り当てます。

void SetVec1(const std::vector<int>& vec1) { this->vec1 = vec1; };

明日は、これをスワップ方式を使用するように変更する必要があると思います。これにより、間違いなくこれが少し改善されるはずです。

達成しようとしているセマンティクスが完全に明確でない場合は申し訳ありませんが、その理由はよくわからないためです。

よろしく、

モーテン

4

6 に答える 6

3

すべては、このオブジェクトをコピーすることがあなたの場合に何を意味するかに依存します:

  1. それはそれの価値全体をコピーすることを意味します
  2. これは、コピーされたオブジェクトが同じコンテンツを参照することを意味します

1の場合、このクラスは正しいようです。あなたが言う操作がたくさんのコピーを作成することについてあなたはあまり明確ではないので、私はあなたがオブジェクト全体をコピーしようとしていると仮定しています。

2の場合、オブジェクト間でコンテナを共有するには、shared_ptrなどを使用する必要があります。メンバーとして実際のオブジェクトの代わりにshared_ptrを使用するだけで、バッファーが両方のオブジェクト(コピーとコピー)によって参照されることが暗黙的に許可されます。これがより簡単な方法です(C ++ 0x対応のコンパイラが提供している場合は、boost::shared_ptrまたはstd::shared_ptrを使用します)。

もっと難しい方法もありますが、後で問題になることは間違いありません。

于 2011-05-10T19:00:38.163 に答える
2
  1. もちろん、そして誰もがこれを言う、時期尚早に最適化しないでください。a)プログラムの進行が遅すぎること、およびb)あまり多くのデータをコピーしなかった場合は速くなることを証明するまで、これを気にしないでください。

  2. プログラムの設計でデータの複数の同時コピーを保持する必要がある場合、できることは何もありません。弾丸を噛んでデータをコピーするだけです。いいえ、カスタムコピーコンストラクターとカスタム代入演算子を実装しても、高速化はしません。

  3. プログラムがこのデータの複数の同時コピーを必要としない場合は、実行するコピーの数を減らすためのいくつかのトリックがあります。

コピーメソッドをインストルメントするそれが私である場合、何かを改善しようとする前であっても、最初に行うことは、コピーメソッドが呼び出された回数を数えることです。

class Foo {
private:
  static int numberOfConstructors;
  static int numberofCopyConstructors;
  static int numberofAssignments;
  Foo() { ++numberOfConstructors; ...; }
  Foo(const Foo& f) : vec1(f.vec1), vec2(f.vec2), bimap(f.bimap) {
    ++numberOfCopyConstructors;
    ...;
  }
  Foo& operator=(const Foo& f) {
    ++numberOfAssignments;
    ...;
  }
};

改善の有無にかかわらず、プログラムを実行します。それらの静的メンバーの値を印刷して、変更が効果を発揮したかどうかを確認します。

参照を使用して関数呼び出しでの割り当てを回避するFoo型のオブジェクトを関数に渡す場合は、参照によってそれを実行できるかどうかを検討してください。渡されたコピーを変更しない場合は、const参照で渡すのは簡単です。

// WAS:
extern SomeFuncton(Foo f);
// EASY change -- if this compiles, you know that it is correct
extern SomeFunction(const Foo& f);
// HARD change -- you have to examine your code to see if this is safe
extern SomeFunction(Foo& f);

Foo :: swapを使用してコピーを回避するコピーメソッドを(明示的または暗黙的に)頻繁に使用する場合は、割り当て元のアイテムがデータをコピーするのではなく、あきらめる可能性があるかどうかを検討してください。

// Was:
vectorOfFoo.push_back(myFoo);
// maybe faster:
vectorOfFoo.push_back(Foo());
vectorOfFoo.back().swap(myFoo);

// Was:
newFoo = oldFoo;
// maybe faster
newfoo.swap(oldFoo);

もちろん、これはデータにアクセスする必要がなくなった場合myFooにのみ機能します。oldFooそして、あなたは実装する必要がありますFoo::swap

void Foo::swap(Foo& old) {
    std::swap(this->vec1, old.vec1);
    std::swap(this->vec2, old.vec2);
    ...
}

何をするにしても、変更の前後にプログラムを測定てください。コピーメソッドが呼び出された回数と、プログラムの合計時間の改善を測定します。

于 2011-05-10T19:19:16.790 に答える
1

あなたのクラスはそれほど悪くはないようですが、あなたはそれをどのように使うかを示していません。

コピーが多い場合は、それらのクラスのオブジェクトを参照(または可能であればconst参照)で渡す必要があります。そのクラスをコピーする必要がある場合は、何もできません。

于 2011-05-10T18:33:10.597 に答える
1

それが本当に問題である場合は、pimplイディオムの実装を検討することをお勧めします。しかし、それが問題だとは思えませんが、確実にクラスの使用を確認する必要があります。

于 2011-05-10T18:43:26.597 に答える
1

巨大なベクトルのコピーは、おそらく安価である可能性があります。最も有望な方法は、レアをコピーすることです。C ++では意図せずにコピーを呼び出すのは非常に簡単です(簡単すぎるかもしれません)が、不必要なコピーを回避する方法があります。

  • constおよびnon-const参照を渡す
  • 移動コンストラクター
  • 所有権の譲渡を伴うスマートポインタ

これらの手法では、アルゴリズムに必要なコピーのみが残る場合があります。

それらのコピーの一部でさえ回避できる場合があります。たとえば、2番目のオブジェクトが最初のオブジェクトの反転コピーである2つのオブジェクトが必要な場合、反転のように機能するラッパーオブジェクトを作成できますが、コピー全体を格納する代わりに参照のみが含まれます。

于 2011-05-10T19:00:29.120 に答える
0

コピーを減らすための明白な方法は、shared_ptrのようなものを使用することです。ただし、マルチスレッドを使用すると、この治療法は病気よりも悪化する可能性があります。参照カウントのインクリメントとデクリメントはアトミックに実行する必要があり、非常にコストがかかる可能性があります。ただし、通常、コピーを変更し、各コピーを一意に動作させる必要がある場合(つまり、コピーを変更しても元のコピーに影響がない場合)、パフォーマンスが低下し、参照カウントのアトミックインクリメント/デクリメントが発生する可能性があります。 、とにかくまだたくさんのコピーをしています。

それを回避するための明白な方法がいくつかあります。1つは、コピーするのではなく、一意のオブジェクトを移動することです。これは、機能させることができれば素晴らしいことです。もう1つは、ほとんどの場合、非アトミック参照カウントを使用し、スレッド間でデータを移動する場合にのみディープコピーを実行することです。

しかし、「普遍的で本当にきれいだ」という答えはありません。

于 2011-05-10T19:21:12.223 に答える