web-dev-qa-db-ja.com

shared_ptrを通過するコスト

アプリケーション全体でstd :: tr1 :: shared_ptrを幅広く使用しています。これには、関数の引数としてオブジェクトを渡すことが含まれます。以下を検討してください。

class Dataset {...}

void f( shared_ptr< Dataset const > pds ) {...}
void g( shared_ptr< Dataset const > pds ) {...}
...

Shared_ptrを介してデータセットオブジェクトを渡すと、fとg内での存在が保証されますが、関数は何百万回も呼び出される可能性があり、多くのshared_ptrオブジェクトが作成および破棄されます。以下は、最近の実行からのフラットなgprofプロファイルのスニペットです。

[。 :: tr1 :: __ shared_count :: __ shared_count(std :: tr1 :: __ shared_count const&)
 8.03 324.34 28.95 2451252116 0.00 0.00 std :: tr1 :: __ shared_count ::〜__shared_count()

したがって、ランタイムの約17%は、shared_ptrオブジェクトを使用した参照カウントに費やされました。これは正常ですか?

私のアプリケーションの大部分はシングルスレッドであり、いくつかの関数を次のように書き直すことを考えていました

void f( const Dataset& ds ) {...}

呼び出しを置き換える

shared_ptr< Dataset > pds( new Dataset(...) );
f( pds );

f( *pds );

プログラムのフローがf()内にあるときにオブジェクトが破棄されないことが確実にわかっている場所。しかし、一連の関数シグネチャ/呼び出しを変更するために実行する前に、shared_ptrを渡すことによる典型的なパフォーマンスヒットを知りたいと思いました。頻繁に呼び出される関数には、shared_ptrを使用しないでください。

任意の入力をいただければ幸いです。読んでくれてありがとう。

-Artem

pdate:受け入れる関数をいくつか変更した後const Dataset&、新しいプロファイルは次のようになります。

[。 :: tr1 :: __ shared_count ::〜__shared_count()
 0.12 241.91 0.30 28342376 0.00 0.00 std :: tr1 :: __ shared_count :: __ shared_count(std :: tr1 :: __ shared_count const&)

デストラクタ呼び出しの数がコピーコンストラクタ呼び出しの数よりも少ないことに少し戸惑っていますが、全体として、関連する実行時間の減少に非常に満足しています。彼らの助言に感謝します。

57
Artem Sokolov

常にshared_ptrconst参照で渡します。

void f(const shared_ptr<Dataset const>& pds) {...} 
void g(const shared_ptr<Dataset const>& pds) {...} 

編集:他の人が言及した安全性の問題について:

  • アプリケーション全体でshared_ptrを頻繁に使用する場合、値による受け渡しには非常に長い時間がかかります(50%以上になることがわかりました)。
  • 引数をnullにしない場合は、const T&ではなくconst shared_ptr<T const>&を使用してください。
  • パフォーマンスが問題になる場合は、const shared_ptr<T const>&を使用する方がconst T*より安全です。
56
Sam Harwell

Shared_ptrは、将来使用するために保持する関数/オブジェクトに渡す場合にのみ必要です。たとえば、一部のクラスは、workerスレッドで使用するためにshared_ptrを保持する場合があります。単純な同期呼び出しの場合は、単純なポインターまたは参照を使用するだけで十分です。 shared_ptrは、プレーンポインターの使用を完全に置き換えるべきではありません。

10
Alex F

make_shared を使用していない場合は、試してみてください。参照カウントとオブジェクトを同じメモリ領域に配置することで、キャッシュコヒーレンシに関連するパフォーマンスの向上を確認できます。とにかく試してみる価値があります。

5
Kylotan

オブジェクトの作成と破棄、特に冗長なオブジェクトの作成と破棄は、パフォーマンスが重要なアプリケーションでは避けてください。

Shared_ptrが何をしているかを考えてみましょう。それは新しいオブジェクトを作成してそれを埋めるだけでなく、共有状態を参照して参照情報をインクリメントします。オブジェクト自体はおそらく他のどこかに完全に存在し、キャッシュ上で悪夢のようになります。

おそらく、shared_ptrが必要です(ローカルオブジェクトを使用できない場合は、ヒープから1つを割り当てることができないため)が、shared_ptr逆参照の結果を "キャッシュ"することもできます。

void fn(shared_ptr< Dataset > pds)
{
   Dataset& ds = *pds;

   for (i = 0; i < 1000; ++i)
   {
      f(ds);
      g(ds);
   }
}

... * pdsでも、必要以上に多くのメモリを使用する必要があるためです。

3
dash-tom-bang

あなたは自分が何をしているか本当に知っているようです。アプリケーションのプロファイルを作成し、サイクルが使用されている場所を正確に把握しました。参照カウントポインターへのコンストラクターの呼び出しは、絶えず行う場合にのみコストがかかることを理解しています。

私があなたに与えることができる唯一のヘッドアップは次のとおりです:関数f(t * ptr)内で、共有ポインターを使用する別の関数を呼び出し、other(ptr)を実行し、その他がrawポインターの共有ポインターを作成するとします。その2番目の共有ポインターの参照カウントが0に達すると、オブジェクトを効果的に削除したことになります。参照カウントポインターを頻繁に使用すると言ったので、そのようなコーナーケースに注意する必要があります。

編集:あなたはデストラクタをプライベートにし、共有ポインタクラスの友達だけにすることができるので、デストラクタは共有ポインタによってのみ呼び出されることができ、安全です。 共有ポインターからの複数削除を妨げません。マットからのコメントによると。

1
Chris H