19

数日前、Stephan T. Lavavejによるこの非常に興味深いプレゼンテーションを偶然見ました。このプレゼンテーションでは、「 We Know Where You Live」の最適化について言及しています(質問のタイトルに頭字語を使用して申し訳ありません。それ以外の場合は質問が閉じられた可能性があると警告しました。 )、そしてマシンアーキテクチャに関するハーブサッターによるこの美しいもの。

簡単に言うと、「We Know Where You Live」の最適化は、作成中のオブジェクトと同じメモリブロックに参照カウンターを配置することで構成されますmake_shared。これにより、2つではなく1つのメモリ割り当てが行われ、shared_ptrよりコンパクトになります。

しかし、上記の2つのプレゼンテーションから学んだことを要約すると、異なるコアで実行されている複数のスレッドがアクセスした場合に、WKWYLの最適化によってパフォーマンスが低下することはないのではないかと思い始めました。shared_ptr

参照カウンターがメモリ内の実際のオブジェクトに近い場合、実際には、オブジェクト自体と同じキャッシュラインにフェッチされる可能性が高くなります。これにより、レッスンを正しく受けた場合、同じキャッシュラインを競合する必要がない場合でも、スレッドの速度が低下する可能性が高くなります。

スレッドの1つが参照カウンターを数回更新する必要があり(たとえば、shared_ptr周りをコピーするとき)、他のスレッドはポイントされたオブジェクトにアクセスする必要があると仮定します:これは、スレッドを競合させることによってすべてのスレッドの実行を遅くすることはありません同じキャッシュラインに対して?

refcountがメモリ内の別の場所にある場合、競合が発生する可能性は低くなります。

これは、同様のケースでの使用に反対する良い議論にmake_shared()なりますか(もちろん、WKWYL最適化を実装している限り)?それとも私の推論に誤りがありますか?

4

3 に答える 3

11

それがあなたの使用パターンであるなら、確かに、make_shared偽共有」になります。これは、同じバイトにアクセスしていなくても、同じキャッシュラインを使用する異なるスレッドに対して私が知っている名前です。

同じことが、近くのパーツが異なるスレッド(そのうちの1つが書き込み中)によって使用されているオブジェクトにも当てはまります。この場合、「オブジェクト」は。によって作成された結合ブロックですmake_shared。近位データが多かれ少なかれ同時に異なるスレッドで使用されている場合に、データの局所性から利益を得ようとする試みが裏目に出る可能性があるかどうを尋ねることもできます。はい、できます。

すべてのオブジェクトのすべての書き込み可能な部分が離れた場所に割り当てられている場合、競合が発生する可能性は低いと結論付けることができます。したがって、通常、偽共有の修正は、物事を広げることです(この場合、使用を停止するmake_sharedか、オブジェクトにパディングを入れて、その部分を異なるキャッシュラインに分割することができます)。

それとは対照的に、同じスレッドで異なるパーツが使用されている場合、それらをメモリに分散させると、キャッシュにフェッチするものが増えるため、コストがかかります。物事を広げるにはそれ自体のコストがかかるので、それはあなたが最初に考えるほど多くのアプリにとって実際には役に立たないかもしれません。しかし、それが役立つコードを書くことは間違いなく可能です。

キャッシュラインやローカリティとは関係がない場合もありmake_sharedます。単に、2つではなく1つの動的割り当てを行うだけです。その値は、割り当てて解放するオブジェクトの数によって異なります。無視できる場合があります。これは、アプリがRAMに収まるのか、狂ったようにスワッピングするのかという違いかもしれません。場合によっては、アプリが必要なすべての割り当てを行う必要がある場合があります。

参考までに、使用しない可能性のある別の状況がmake_sharedあります。それは、オブジェクトが小さくなく、ポインターが弱く、が大幅に長持ちする場合shared_ptrです。その理由は、弱ポインタ​​がなくなるまで制御ブロックが解放されないため、使用した場合make_shared、弱ポインタ​​がなくなるまでオブジェクトが占有するメモリ全体が解放されないためです。もちろん、共有ポインタがなくなるとすぐにオブジェクトが破棄されるため、重要なのはクラスのサイズだけであり、関連するリソースではありません。

于 2013-01-15T18:23:10.010 に答える
5

参照カウントの割り当ては、WKWYLの最適化に関するものではないことに注意してください。これは、それ自体の主な意図された効果ですstd::make_shared。あなたは完全なコントロールを持っています:make_shared<T>()割り当てを保存してオブジェクトと一緒に参照カウントを置くために使用するか、shared_ptr<T>( new T() )それを分離しておくために使用します。

はい、オブジェクトと参照カウントを同じキャッシュラインに配置すると、オブジェクトが読み取りのみにアクセスされているときに参照カウントが頻繁に更新されると、偽共有によるパフォーマンスの低下につながる可能性があります。

しかし、私が見ているように、これがこの最適化を行うための決定に考慮されない理由は2つあります。

  1. 一般に、参照カウントを頻繁に変更することは望ましくありません。これは、それ自体がパフォーマンスの問題(アトミック操作、それにアクセスする複数のスレッドなど)であり、回避したい(そしておそらくほとんどの場合可能である)ためです。
  2. この最適化を実行しても、説明した潜在的な追加のパフォーマンスの問題が必ずしも発生するわけではありません。そのためには、参照カウントとオブジェクト(の一部)が同じキャッシュラインにある必要があります。したがって、参照カウント(+その他のデータ)とオブジェクトの間に適切なパディングを追加することで、簡単に回避できます。その場合、最適化は2つではなく1つの割り当てのみを実行するため、それでも有益です。ただし、この動作をトリガーしない可能性が高いケースでは、パッドなしバージョンの方がローカリティが優れている(オブジェクトと参照カウントが同じキャッシュラインにある)ため、パッドなしバージョンよりも遅くなります。このため、このバリアントは高度にスレッド化されたコードの最適化の可能性があると思いますが、必ずしも標準バージョンで作成されるとは限りません。
  3. プラットフォームにどのように実装されているかを知っている場合shared_ptrは、オブジェクトにパディングを挿入するか、(メモリ内の順序に応じて)十分なパディングを含む削除機能をオブジェクトに与えることで、パディングをエミュレートできます。
于 2013-01-15T16:58:03.373 に答える
4

スレッドの1つが参照カウンターを数回更新する必要があり(たとえば、shared_ptr周りをコピーするとき)、他のスレッドはポイントされたオブジェクトにアクセスする必要があると仮定します:これは、スレッドを競合させることによってすべてのスレッドの実行を遅くすることはありません同じキャッシュラインに対して?

はい、しかしそれは現実的なシナリオですか?

私のコードでは、オブジェクトをコピーするスレッドshared_ptrは、オブジェクトの所有権を共有して使用できるようにするためにそうします。これらすべての参照カウントの更新を行うスレッドがオブジェクトを気にしない場合、なぜオブジェクトの所有権を共有するのが面倒なのですか?

const shared_ptr&参照を渡し、オブジェクトを実際に所有してアクセスしたい場合、たとえばスレッドやモジュールの境界を越えてオブジェクトを転送したり、オブジェクトの所有権を取得して使用したりする場合にのみ、コピーを作成(または破棄)することで、問題を軽減できます。

一般に、侵入型参照カウントは、単一のキャッシュライン上にあり、オブジェクトとそのrefcountに2つの貴重なキャッシュラインを使用する必要がないため、外部参照カウント(Smart Pointer Timingsを参照)よりも優れています。他のすべてのキャッシュラインが1つ少ない余分なキャッシュラインを使い果たした場合、何かが削除され、次に必要になったときにキャッシュミスが発生することを覚えておいてください。

于 2013-01-15T20:52:59.893 に答える