61

アプリケーション全体で 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 プロファイルのスニペットを次に示します。

各サンプルは 0.01 秒としてカウントされます。
  %累積自己自己合計
 time seconds seconds コール数/コール数/コール名
  9.74 295.39 35.12 2451177304 0.00 0.00 std::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 を使用しないようです。

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

-アルテム

更新:一部の関数を accept に変更した後const Dataset&、新しいプロファイルは次のようになります。

各サンプルは 0.01 秒としてカウントされます。
  %累積自己自己合計
 time seconds seconds コール数/コール数/コール名
  0.15 241.62 0.37 24981902 0.00 0.00 std::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&)

デストラクタの呼び出し数がコピー コンストラクタの呼び出し数よりも少ないことに少し困惑していますが、関連するランタイムの減少には全体的に非常に満足しています。アドバイスをくれてありがとう。

4

5 に答える 5

60

常にconst参照shared_ptrで渡します。

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*
于 2010-03-23T18:11:59.357 に答える
14

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

于 2010-03-23T18:13:09.700 に答える
4

make_sharedを使用していない場合は、試してみていただけますか? 参照カウントとオブジェクトをメモリの同じ領域に配置すると、キャッシュの一貫性に関連するパフォーマンスの向上が見られる場合があります。とにかく試してみる価値があります。

于 2010-03-23T18:14:12.710 に答える
3

パフォーマンスが重要なアプリケーションでは、オブジェクトの作成と破棄、特に冗長なオブジェクトの作成と破棄を避ける必要があります。

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

おそらく、shared_ptr が必要ですが (ローカル オブジェクトを取得できた場合、ヒープからオブジェクトを割り当てないため)、shared_ptr デリファレンスの結果を「キャッシュ」することもできます。

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

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

... *pds でさえ、絶対に必要な量よりも多くのメモリをヒットする必要があるためです。

于 2010-03-23T18:13:15.363 に答える
1

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

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

編集:デストラクタをプライベートにし、共有ポインタ クラスのフレンドのみにすることができるので、デストラクタは共有ポインタによってのみ呼び出すことができ、安全です。共有ポインターからの複数の削除を防止しません。マットからのコメントによると。

于 2010-03-23T18:11:55.953 に答える