std :: shared_ptrに相当する非アトミックなものはありますか?そして、なぜ<memory>にないのですか?


88

これは、次の原子性に関する2つの部分からなる質問ですstd::shared_ptr

1. 私が知る限り、それはアトミックstd::shared_ptrな唯一のスマートポインター<memory>です。std::shared_ptr利用可能な非アトミックバージョンがあるかどうか疑問に思っています(何も表示されない<memory>ので、Boostのような標準外の提案も受け付けています)。boost::shared_ptrアトミックでもあることは知っていますが(BOOST_SP_DISABLE_THREADS定義されていない場合)、別の選択肢があるのでしょうか?と同じセマンティクスを持つstd::shared_ptrが、原子性がないものを探しています。

2.なぜstd::shared_ptrアトミックなのか理解しています。ちょっといいです。ただし、すべての状況に適しているわけではなく、C ++には、歴史的に「使用した分だけ支払う」というマントラがありました。複数のスレッドを使用していない場合、または複数のスレッドを使用しているがスレッド間でポインターの所有権を共有していない場合、アトミックスマートポインターはやり過ぎです。私の2番目の質問は、なぜ非アトミックバージョンのstd::shared_ptrがC ++ 11で提供されなかったのかということです。(理由があると仮定して)(答えが単に「非アトミックバージョンは単に考慮されなかった」または「誰も非アトミックバージョンを要求したことがない」である場合は問題ありません!)

質問#2で、誰かがshared_ptr(Boostまたは標準化委員会のいずれかに)の非アトミックバージョンを提案したことがあるかどうか疑問に思っています(のアトミックバージョンを置き換えるのでshared_ptrはなく、それと共存するために)そしてそれは特定の理由。


4
ここで正確に懸念している「コスト」は何ですか?整数をアトミックにインクリメントするコストは?それは実際に実際のアプリケーションに関係するコストですか?それとも、時期尚早に最適化していますか?
ニコルボーラス2013

9
@NicolBolas:それは何よりも好奇心です。私は(現在)非アトミック共有ポインターを真剣に使用したいコード/プロジェクトを持っていません。しかし、私は(過去に)Boostshared_ptrがその原子性のために大幅な減速であり、定義BOOST_DISABLE_THREADSによって顕著な違いが生じたプロジェクトstd::shared_ptrがありました(それと同じコストがあったかどうかはわかりませんboost::shared_ptr)。
Cornstalks 2013

13
@近い有権者:質問のどの部分が建設的ではありませんか?2番目の質問に具体的な理由がない場合は、それで問題ありません(単純な「単に考慮されなかった」だけで十分です)。特定の理由/合理性が存在するかどうか知りたいです。そして、最初の質問は確かに有効な質問だと思います。質問を明確にする必要がある場合、または少し調整する必要がある場合は、お知らせください。しかし、それがどのように建設的でないのかわかりません。
Cornstalks 2013

10
@Cornstalksええと、質問がどれほど有効で、適切で、関連性があるとしても、「時期尚早の最適化」として簡単に却下できる質問に対して、人々はそれほどうまく反応しないのではないでしょうか。私自身、これを非建設的なものとして閉じる理由は見当たらない。
クリスチャンラウ2013

13
(現在は閉じられているため、回答を書き込むことができないため、コメントします)プログラムが複数のスレッドを使用しない場合のGCCではshared_ptr、refcountにアトミック操作を使用しません。(2)を参照gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html、非アトミック実装があっても、マルチスレッドアプリケーションで使用できるようにGCCのパッチのshared_ptr間で共有されていないオブジェクトスレッド。私は何年もそのパッチに座っていましたが、最終的にGCC 4.9にコミットすることを検討しています
Jonathan Wakely 2013

回答:


104

1.非アトミックバージョンのstd :: shared_ptrが利用可能かどうか疑問に思います

標準では提供されていません。「サードパーティ」ライブラリによって提供されるものがあるかもしれません。実際、C ++ 11より前、およびBoostより前は、誰もが独自の参照カウントスマートポインター(私を含む)を作成したように見えました。

2. 2番目の質問は、非アトミックバージョンのstd :: shared_ptrがC ++ 11で提供されなかった理由です。

この質問は、2010年のラッパースウィル会議で議論されました。この主題は、スイスのNational Body Comment#20によって紹介されました。あなたがあなたの質問で提供するものを含めて、議論の両側で強い議論がありました。しかし、議論の終わりに、投票は、の非同期(非アトミック)バージョンを追加することに圧倒的に(全会一致ではありませんが)反対しましたshared_ptr

含まれることに対する議論:

  • 非同期のshared_ptrで記述されたコードは、将来的にスレッドコードで使用される可能性があり、警告なしに問題のデバッグが困難になる可能性があります。

  • 参照カウントでトラフィックへの「一方通行」である「ユニバーサル」shared_ptrを1つ持つことには、次のような利点があります。元の提案から

    使用する機能に関係なく同じオブジェクトタイプを持ち、サードパーティライブラリを含むライブラリ間の相互運用性を大幅に促進します。

  • アトミックのコストはゼロではありませんが、圧倒的ではありません。アトミック操作を使用する必要のないムーブ構築とムーブ代入を使用することで、コストが軽減されます。このような操作は、一般的にvector<shared_ptr<T>>消去と挿入でます。

  • それが本当に彼らがやりたいことであるならば、人々が彼ら自身の非原子参照カウントスマートポインタを書くことを禁止するものは何もありません。

その日のラッパースウィルのLWGからの最後の言葉は次のとおりです。

CH20を拒否します。現時点で変更を加えるためのコンセンサスはありません。


7
うわー、完璧、情報をありがとう!それはまさに私が見つけたいと思っていた種類の情報です。
Cornstalks 2013

>Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries. それは非常に奇妙な推論です。サードパーティのライブラリはとにかく独自の型を提供しますが、std :: shared_ptr <CustomType>、std :: non_atomic_shared_ptr <CustomType>などの形式で提供することが重要なのはなぜですか?あなたはいつも、とにかく何ライブラリリターンにあなたのコードを適応させる必要があります
ジャン-マイケルCelerier

ライブラリ固有の型に関してはそうですが、サードパーティのAPIに標準の型が表示される場所もたくさんあるという考えです。たとえば、私の図書館はstd::shared_ptr<std::string>どこかに行くかもしれません。他の誰かのライブラリもそのタイプを採用している場合、呼び出し元は、異なる表現間で変換するという不便さやオーバーヘッドなしに、同じ文字列を両方に渡すことができます。これは、誰にとっても小さなメリットです。
ジャックオコナー

52

ハワードはすでに質問によく答えており、ニコルは、互換性のない多くのタイプではなく、単一の標準共有ポインター型を持つことの利点についていくつかの良い点を述べました。

私は委員会の決定に完全に同意しますが、特別な場合に非同期のshared_ptrようなタイプを使用することにはいくつかの利点があると思うので、私はこのトピックを数回調査しました。

複数のスレッドを使用していない場合、または複数のスレッドを使用しているがスレッド間でポインターの所有権を共有していない場合、アトミックスマートポインターはやり過ぎです。

プログラムが複数のスレッドを使用しない場合のGCCでは、shared_ptrはrefcountにアトミック操作を使用しません。これは、プログラムがマルチスレッドであるかどうかを検出するラッパー関数を介して参照カウントを更新し(GNU / Linuxでは、プログラムがリンクするかどうかを検出するだけで実行されますlibpthread.so)、それに応じてアトミック操作または非アトミック操作にディスパッチします。

何年も前に、GCCshared_ptr<T>__shared_ptr<T, _LockPolicy>基本クラスで実装されているため、明示的にを使用することで、マルチスレッドコードでもシングルスレッドロックポリシーで基本クラスを使用できることに気付きました__shared_ptr<T, __gnu_cxx::_S_single>。残念ながら、これは意図されたユースケースではなかったため、GCC 4.9より前では最適に機能せず、一部の操作ではラッパー関数が使用されていたため、_S_singleポリシーを明示的に要求した場合でもアトミック操作にディスパッチされました。http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlのポイント(2)を参照してください。詳細とGCCへのパッチについては、マルチスレッドアプリでも非アトミック実装を使用できるようにしてください。私はそのパッチを何年も使用していましたが、最終的にGCC 4.9にコミットしました。これにより、このようなエイリアステンプレートを使用して、スレッドセーフではないがわずかに高速な共有ポインター型を定義できます。

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;

このタイプは相互運用性がなく、ユーザーが提供する追加の同期なしにオブジェクトがスレッド間で共有されないstd::shared_ptr<T>ことが保証されている場合にのみ安全に使用shared_ptr_unsynchronizedできます。

もちろん、これは完全に移植性がありませんが、場合によっては問題ありません。適切なプリプロセッサハックを使用するshared_ptr_unsynchronized<T>と、がのエイリアスである場合、コードは他の実装でも正常に機能しshared_ptr<T>ますが、GCCを使用すると少し速くなります。


4.9より前のGCCを使用している場合は、_Sp_counted_base<_S_single>明示的な特殊化を独自のコードに追加することで使用できます(また__shared_ptr<T, _S_single>、ODR違反を回避するために、特殊化を含めずにインスタンス化されないようにします)。このようなstd型の特殊化を追加することは技術的には定義されていませんが、この場合、GCCに専門分野を追加することと、独自のコードに専門分野を追加することの間に違いはないため、実際に機能します。


2
疑問に思っているのですが、テンプレートエイリアスの例にタイプミスがありますか?つまり、shared_ptr_unsynchronized = std :: __ shared_ptr <と読む必要があると思います。ちなみに、私はこれをstd :: __enable_shared_from_thisおよびstd :: __ weak_ptrと組み合わせて今日テストしましたが、うまく機能しているようです(gcc4.9およびgcc5.2)。すぐにプロファイル/分解して、実際にアトミック操作がスキップされるかどうかを確認します。
カールクック

素晴らしい詳細!最近、この質問で説明されているように、問題に直面しました。その結果、最終的にのソースコードを調べるようになりました。std::shared_ptrstd::__shared_ptr__default_lock_policyなど。この答えは、私がコードから理解したことを確認しました。
nawaz 2017年

21

私の2番目の質問は、なぜ非アトミックバージョンのstd :: shared_ptrがC ++ 11で提供されなかったのかということです。(理由があると仮定して)。

侵入型ポインタがない理由や、共有ポインタのその他の可能なバリエーションがいくつもあるのかを簡単に尋ねることができます。

shared_ptrBoostから受け継がれたの設計は、スマートポインターの最小標準言語フランカを作成することでした。つまり、一般的に言って、これを壁から引き下げて使用するだけです。これは、さまざまなアプリケーションで一般的に使用されるものです。あなたはそれをインターフェースに置くことができます、そして確率は良い人々がそれを喜んで使うでしょう。

スレッド化は、将来さらに普及するでしょう。実際、時間が経つにつれて、スレッド化は一般にパフォーマンスを達成するための主要な手段の1つになります。スレッド化をサポートするために最低限必要なことを実行するために基本的なスマートポインターを要求することは、この現実を容易にします。

わずかな違いのある半ダースのスマートポインターを標準にダンプするか、さらに悪いことに、ポリシーベースのスマートポインターをダンプするのはひどいことでした。誰もが自分が一番好きなポインタを選び、他のすべてを断念するでしょう。誰も他の人とコミュニケーションをとることができません。これは、C ++文字列の現在の状況のようになり、誰もが独自の型を持っています。文字列との相互運用は、スマートポインタクラス間の相互運用よりもはるかに簡単であるため、さらに悪いことになります。

Boost、ひいては委員会は、使用する特定のスマートポインターを選択しました。機能のバランスが良く、実際に広く一般的に使用されていました。

std::vector一部のコーナーケースでも、ネイキッドアレイと比較して非効率性があります。いくつかの制限があります。一部の用途ではvector、スローアロケーターを使用せずに、のサイズに厳しい制限を設ける必要があります。しかし、委員会はvectorすべての人にとってすべてになるように設計されていませんでした。これは、ほとんどのアプリケーションに適したデフォルトになるように設計されています。それがうまくいかない人は、彼らのニーズに合った代替案を書くことができます。

shared_ptrの原子性が負担である場合、スマートポインタの場合と同じように。繰り返しになりますが、それらをあまりコピーしないことも検討するかもしれません。


7
「それらをあまりコピーしないことも検討するかもしれない」の+1。
アリ

プロファイラーを接続したことがある場合、あなたは特別であり、上記のような引数を調整することができます。満たすのが難しい運用要件がない場合は、C ++を使用しないでください。あなたのように主張することは、高性能または低遅延に関心のある人にC ++を普遍的に罵倒させる良い方法です。これが、ゲームプログラマーがSTL、ブースト、さらには例外を使用しない理由です。
Hans Malherbe 2017年

わかりやすくするために、回答の上部にある引用符は、「非アトミックバージョンのstd :: shared_ptrがC ++ 11で提供されなかったのはなぜですか?」と読む必要があると思います。
Charles Savoie

4

職場でshared_ptrについての講演を準備しています。私は変更されたブーストshared_ptrを使用しており、個別のmalloc(make_sharedで実行できることなど)と上記のshared_ptr_unsynchronizedのようなロックポリシーのテンプレートパラメーターを回避しています。私はからのプログラムを使用しています

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

テストとして、不要なshared_ptrコピーをクリーンアップした後。プログラムはメインスレッドのみを使用し、テスト引数が表示されます。テスト環境は、linuxmint14を実行しているノートブックです。所要時間は秒単位です。

make_sharedで変更されたブーストを使用したテスト実行セットアップブースト(1.49)標準
mt-unsafe(11)11.9 9 / 11.5(-pthread on)8.4  
アトミック(11)13.6 12.4 13.0  
mt-unsafe(12)113.5 85.8 / 108.9(-pthread on)81.5  
アトミック(12)126.0 109.1 123.6  

'std'バージョンのみが-std = cxx11を使用し、-pthreadはg ++ __ shared_ptrクラスのlock_policyを切り替える可能性があります。

これらの数値から、コードの最適化に対するアトミック命令の影響がわかります。テストケースはC ++コンテナを使用vector<shared_ptr<some_small_POD>>しませんが、オブジェクトがスレッド保護を必要としない場合は問題が発生する可能性があります。追加のmallocがインライン化とコードの最適化の量を制限しているため、Boostの影響は少ないと考えられます。

アトミック命令のスケーラビリティをストレステストするのに十分なコアを備えたマシンをまだ見つけていませんが、必要な場合にのみstd :: shared_ptrを使用する方がおそらく良いでしょう。


3

Boostは、shared_ptr非アトミックなを提供します。これはと呼ばれlocal_shared_ptr、boostのスマートポインタライブラリにあります。


良い引用を伴う短い堅実な応答の場合は+1ですが、このポインター型は、間接参照のレベルが1つ余分にあるため(ローカル->共有-> ptrと共有-> ptr)、メモリとランタイムの両方の点でコストがかかるように見えます。
Red.Wave

@ Red.Wave間接参照の意味と、それがパフォーマンスにどのように影響するかを説明できますか?あなたはそれがだことを意味しますshared_ptr、それはローカルだにもかかわらず、とにかくカウンターで?それとも、別の問題があるということですか?ドキュメントによると、唯一の違いはこれがアトミックではないということです。
量子物理学者

すべてのローカルptrは、元の共有ptrへのカウントと参照を保持します。したがって、最終的なポインティへのアクセスには、ローカルから共有ptrへの参照解除が必要であり、それがポインティへの参照解除になります。したがって、共有ptrからの間接参照までスタックされたもう1つの間接参照があります。そしてそれはオーバーヘッドを増加させます。
Red.Wave

@ Red.Waveこの情報はどこから入手していますか?これ:「したがって、最終ポイントへのアクセスには、ローカルから共有ptrへの参照解除が必要です」にはいくつかの引用が必要です。ブーストドキュメントではそれを見つけることができませんでした。繰り返しますが、私がドキュメントで見たのは、それがそれを言ってlocal_shared_ptrおりshared_ptr、アトミックを除いて同一であるということです。私はlocal_shared_ptr高性能を必要とするアプリケーションで使用しているので、あなたが言っていることが真実であるかどうかを知ることに本当に興味があります。
量子物理学者

3
@ Red.Wave実際のソースコードgithub.com/boostorg/smart_ptr/blob/を見ると、二重の間接参照がないことがわかります。ドキュメントのこの段落は、単なるメンタルモデルです。
IlyaPopov19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.