• ベストアンサー

参照カウントについて

参照カウントについて聞きたいのですが、ウィキペディアで単純な実装では大量のオブジェクトが一斉に解放されることがあり、CPUの空き時間を利用してガベージコレクションを行う方法と比べると メモリの解放で処理が遅くなってしまう場合もある。 というのはマルチスレッドでいっきに開放するとマルチスレッドでメモリの空き時間を利用して開放を比べているのでしょうか?また一斉に開放とはスレッドを占有するということですか?

質問者が選んだベストアンサー

  • ベストアンサー
  • BLK314
  • ベストアンサー率55% (84/152)
回答No.6

ご回答頂きまして、有難うございます。 どうやら、私は、質問者さんを大変誤解していた模様です。 専門用語バシバシの話をしてしまい、 理解するのも大変な御苦労であったことと推察いたします。 ごめんなさいね。 もっと、易しく解説すべきだった(涙.....) でも、難しいことを平易な言葉で説明するって、 非常に困難なのですよ。そこだけは、理解してください。 "スレッド"について理解が足りないようなので、少しだけ解説します。 本格的な解説は http://www.ncad.co.jp/~komata/c-kouza28.htm に譲るとします。 >スレッドはプログラムの小さな単位 その通りなのですが、これだと"関数"とかとごっちゃになってしまいます。 スレッドの最大の特徴は "並列実行”を行えることです。 関数1個だけでは、並列実行をできません。 (特殊なやり方でできますが触れません) オブジェクトとは、てっとり早く言ってしまえば"変数"ですね。 (厳密ではありませんが、大体これでいいと思います) int やfloatもクラス変数も全部オブジェクトです。 テキスト領域にはその名に反し、プログラムの命令しか格納されず、 文字列データは入りません (ま、知らなくてもいいことなのではありますが....) そして、やっと、”参照カウンタ"です。 こいつの目的は、ズバリ、"メモリ管理の苦労からプログラマーを楽にする"。 これに、尽きると思います。 たとえば、PhotoShopのようなものを考えます。 円、直線、画像、これらのオブジェクトを自由に配置して一枚の絵 (レイヤーと言ったほうが正確かも)にするわけです。 しかも”無限回"Undoできるので、同じ円、画像等がたくさんできます。 色とかの属性が変化したオブジェクトは仕方がないので、変化前の コピーを取っておくにしても、変化しなかったオブジェクトをコピーするのは 無駄ですね。 これを避ける方法はいくつも考えられますが、 ”変化しないのだったら同じオブジェクトを共有する” というのも一つのアイディアだと思います。 幸い、C/C++では同じオブジェクトを共有するのは簡単です。 アドレスの代入で簡単に行えます。 しかし、これには問題があります。 削除をどうするかです。 具体的に考えます Undoが1回行われたとします。 Undoバッファの内容が表示されます。 ここで再度Undoが行われたとします。 メモリーリークがあってはいけないので、 かつてのUndoバッファのオブジェクトは全てdeleteしなければなりません。 しかし、安直にUndoバッファ内のオブジェクト全部にdeleteをかけると、 他のUndoバッファと共有しているオブジェクトまで破棄してしまい、 Undoできなくなってしまいます。 では、どうするか? 共有されているかどうかのフラグを設け、 共有していないものだけ削除すればいい というアイディアが生まれます。 では、共有されているか・いないかをどう判定するか? 共有するときにフラグを立てるのはいいとして、 共有が解除されたときどう対応するべきか? いくつものバッファで共有された場合、 どうやって、最後の共有が解除されたのを知るのか? この答えとして "常に共有されている数を把握すればよい" というアイディアが生まれます。 つまり、最初は共有数=0で開始し 1つ共有されると1になり 2つめ共有されると2になり、 1つ共有解除で1に戻り 2つめの解除で0になります 0になったという時点で、どこにも共有されていないことがわかります。 どこかで聞いた話ですね そう、この"共有数"こそが"参照カウンタ"です。 これにより、プログラマーは"削除していいのか?" に頭を悩ます必要がなくなります。 参照カウンタを見て、0だったら削除すればよいし 1以上だったら"削除してはいけないのです” これを自動化することもできます。 参照カウンタを減少させる関数内で、 参照カウンタが0になったらdelete thisするのです。 (いわゆる"単純な実装") こうすることで、プログラマーはdelete作業そのものから解放されます。 しかし、これにもちょっとばかり弱点があります。 シングル・スレッドなら問題ないのですが、 マルチ・スレッドを考えた場合です。 スレッドAとスレッドBがあったとします。 両者がオブジェクトXを共有しています。 両スレッドともいつかはXへの共有(参照といってもよいです) を解除するわけです。 (解除しないとメモリーリークになります) いわゆる"単純な実装"では参照カウンタ=0となったスレッドで 自動的にdelete thisされます。 このXが単純なオブジェクトであれば文句ないですが、 たとえば円10個、直線20個を全部合成して1個のオブジェクトYとして 扱っていたとしてましょう。 プログラムの見かけはdelete y;// yはY型のオブジェクト(ポインタ) だけかも知れませんが、 実際には円のオブジェクト10個、 直線のオブジェクト20個が一斉に破壊されるわけです。 これには、それなりの時間がかかってしまいます。 たまたま、運悪くAが最後に参照解除した場合、 Aはdelete yに時間がかかる分重くなります。 gcがスレッドだったとします。 多くの場合、gcスレッドの優先順位は低めに設定されます。 AもBも実行中でない”暇な"時間を利用して削除されます。 gcを利用する場合 "参照カウント=0”になっても、即削除は行いません。 "参照カウント=0"のまま放置されます。 たまたま、スレッドAが最後に参照解除したとしても その時点でdelete thisを行うわけでなく、そのまま解除から抜けます。 つまり、スレッドBと全く差はありません。 重くはなりません。 delete yはgcスレッド中で行われます。 ただし、gcの優先度は通常低いので、 実際にいつ動作するチャンスが巡ってくるかわかりません。 場合によっては スレッドAで参照カウンタ=0になってから gcスレッドで実際にdelete yされるまでかなりの間 放っておかれる可能性も否定できません。 つまり、不要なオブジェクトが長期にわたってメモリに居座り続ける 可能性もあるのです。 オブジェクトの生成/消滅を激しく繰り返す用途では これがネックとなって 最悪メモリ不足に至る可能性もあり得なくはありません。 以上です

79562
質問者

お礼

参照カウントはなんとなくわかりました。それと、まだまだ勉強不足だと実感しました。もう少し勉強してから。考えたいと思います。回答ありがとうございました。

すると、全ての回答が全文表示されます。

その他の回答 (5)

  • BLK314
  • ベストアンサー率55% (84/152)
回答No.5

>ガベージコレクションと参照カウンタはスタック領域に配置されていて >スレッド毎に破棄する時削除コードが動作します。 違います。 ”ガベージコレクション"は"コード"なので、スタックには配置されません。 配置されるのは(Win32の場合) テキスト領域です。 参照カウンタは、それを含むオブジェクトと同一の領域に配置されることが 多いと思います。(無論、実装次第です) スレッド固有のオブジェクト(通常はスレッド固有のスタックに配置されます) は、作成したスレッド自身でしか削除できません。 従って、スレッドがそういうオブジェクトを所有しており、 (通常通りスタックに配置されているならば) オブジェクトが廃棄されるコードが働きます。 (C++でいうなら"デストラクタ") >そしてガベージコレクションはスレッドで実装している場合 >GCスレッドが1つずつ開放を行う。 その通りです。 >一方参照カウントはオブジェクトの複数の処理をとった場合、 >負荷がかかる(重くなる) >でよろしいのでしょうか? 違います。 ”参照カウント"の実装によります。 今まで、述べてきたことが、全く理解されていないようで 悲しいです。 これ以上、説明を続ける前に あなたがどれほど理解されているか、確認させてください。 でないと、何度も同じ話を延々と続けなければならず、 非効率です。 以下の質問にお答えください。 1)"関数"とは何ですか? 2)"スレッド"とは何ですか? 3)"関数"と"スレッド"の違いは何ですか? 4)オブジェクトとは何ですか? 5)”スタック領域", "静的(static)領域”、”ヒープ領域", "テキスト領域"とは、 それぞれどういうことですか?それぞれの特徴と使い分けについて 説明してください 6)参照カウンタとは何ですか?何のためにあるのですか? 以上にお答えください。 お答えによって、今後の説明の仕方を変えなければなりません。 今まで、私は、あなたが1) ~ 6)すべて、 わかっているものと想定していました。

79562
質問者

お礼

回答ありがとうございます。関数とは引数と呼ばれるデータを受け取り、型を返す。スレッドはプログラムの小さな単位。関数とスレッドの違いはわかりません。オブジェクトとはClassによって出来たもの。スタック領域はローカル変数や関数の戻り値、引数を使われます。ヒープ領域はメモリを確保をする場合に使われる。テキスト領域は関数、文字列定数が入る領域。参照カウンタとは同じのオブジェクトを指しているポインタがいくつ存在するか。以上です。

79562
質問者

補足

参照カウンタは何のためにあるのかはよくわかりません。

すると、全ての回答が全文表示されます。
  • BLK314
  • ベストアンサー率55% (84/152)
回答No.4

>これをガベージコレクションと参照カウントをプログラミング >でやるとどうなるのでしょうか? 非常に難しい問題です。 特にGCは、本来は言語処理系で実装すべき問題です。 (OSとかランライムとか...) 参照カウンタについては、既にすぐれた実装がいくつもあります。 例えば、MSのCompoent Object Modelなどが有名です http://ja.wikipedia.org/wiki/Component_Object_Model#.E5.8F.82.E7.85.A7.E3.82.AB.E3.82.A6.E3.83.B3.E3.82.BF

79562
質問者

お礼

回答ありがとうございます。 そうなんですか・・・ じゃあ確認だけよろしくお願いします。 ガベージコレクションと参照カウンタはスタック領域に配置されていて スレッド毎に破棄する時削除コードが動作します。 そしてガベージコレクションはスレッドで実装している場合GCスレッドが1つずつ開放を行う。 一方参照カウントはオブジェクトの複数の処理をとった場合、負荷がかかる(重くなる) でよろしいのでしょうか?

79562
質問者

補足

参照カウントはカウントが0の時自分自身をdeleteして、ガベージコレクションはカウンタが0の時deleteするも付け加えます。

すると、全ての回答が全文表示されます。
  • BLK314
  • ベストアンサー率55% (84/152)
回答No.3

申し訳ありません 一部、誤記がありました ”一斉に解放される場合、最後に参照されたスレッドが重くなります” は、誤りです。 ”一斉に解放される場合、最後に参照解除したスレッドが重くなります” こちらが正解です。

79562
質問者

お礼

回答ありがとうございます。 これをガベージコレクションと参照カウントをプログラミングでやるとどうなるのでしょうか? ご教授お願いします。

79562
質問者

補足

>カウンタが0になったら、"自分で自分を"削除します。 >この”自分で自分を"というところがポイントです。 >何か、使われている変数を監視しているスレッドが存在して、 >それが、カウントの値をチェックし、0であったら削除する は何が違うのでしょうか?わかりやすくお願いします。

すると、全ての回答が全文表示されます。
  • BLK314
  • ベストアンサー率55% (84/152)
回答No.2

もしかして、参照カウントについて誤解があるのでは? 参照カウントの概念とGCが混同されている気がしています。 参照カウントとは、その名の通り ”自分が"参照されているカウントを管理する仕組みのことです。 ポインタの代入などで、参照されるたびにカウントが増し、 そのポインタが、消滅したり、他のオブジェクトを参照したりすると、 カウンタを減らします。 "単純な実装"では、カウンタが0になったら、"自分で自分を"削除します。 この”自分で自分を"というところがポイントです。 何か、使われている変数を監視しているスレッドが存在して、 それが、カウントの値をチェックし、0であったら削除する (これがGCですね) とは違います。 ですから、"参照カウンタ"にはマルチスレッドは必須ではありません。 より正確には、 "参照カウンタ"とスレッドとは全く無関係です。 ただ、ガベージコレクションを実装するのに便利なツールとして スレッドが利用されることがあるというだけです。 ですから、 シングル・スレッドでも参照カウンタは利用可能ですし、 実際に用いられます。 また、ガベージ・コレクションも同様です。 あくまで、スレッドは実装するのに便利な道具に過ぎず、 必須ではありません。 シングル・スレッドであっても 適当なタイミングでクリーンナップ処理が行われればよいわけです。 たとえば、Windowsであれば WM_TIMERを利用してクリーンナップを実行することも考えられます。 アプリはシングル・スレッドでも ライブラリやランタイムなどが用意されていて WM_TIMER応答でクリーンアップするようになっていれば アプリはクリーンナップを意識せずに作成可能です。 以上のことを前提とします。 アプリがマルチ・スレッドの場合を考えます。 それぞれのスレッドが固有のオブジェクトを持っている場合、 "単純な実装"では スレッドごとにオブジェクトの破壊コードが実行されることになります。 基本的に各スレッド固有のオブジェクトは 独自のスタック領域に配置されますので 他のスレッドはアクセスできません。 もちろん、他のスレッドが勝手にオブジェクトを廃棄することも不可能です。 これはGCでも全く同じです。 GCを採用したシステムでも、 各スレッドが固有のオブジェクトしか持たない場合には "単純な実装"同様 スレッドごとに削除コードが動作することになります。 スレッド間でオブジェクトを共有する場合はどうでしょうか? 先ほどのべたように、スレッドごとにスタックは独立していますので、 共有オブジェクトはスタックに配置できません。 したがって、ヒープまたはstaticに配置せざるを得ません。 (以後、簡単のため、ヒープとstaticを"共有域"と呼ぶことにします) この場合 スレッドAが生成したオブジェクトに スレッドBがアクセスすることは当然可能ですし、 スレッドBが削除することも可能です。 ガベージコレクション専用のスレッドを用意し、 すべてのスレッドが共有域に作成したオブジェクトの廃棄を すべて任せることも可能です。 このように実装された場合、 通常のスレッド(GC専用スレッドでないスレッド)は "共有オブジェクト"の廃棄を分離することが可能です。 "単純な実装"では、最後に共有オブジェクトを参照解除したスレッドで そのオブジェクトを廃棄することになります。 この実装の場合、そのオブジェクトが複雑な構成をとっていた場合 そのスレッドに解体の際の負荷がかかることは 以前に述べたとおりです ですから >マルチスレッドでいっきに開放すると >マルチスレッドでメモリの空き時間を利用して開放を比べているのでしょうか? そうではありません。マルチスレッドやシングルスレッドに関係ない話です。 >また一斉に開放とはスレッドを占有するということですか? スレッドとは関係のない話なので "スレッドを占有する/しない"の議論は (参照カウンタ自体の話としては) 意味を持ちません。 マルチ・スレッド環境で共有域に参照カウンタをもつオブジェクトが配置される という特殊な事例についてでの共有オブジェクトの廃棄について ”単純な実装"では、共有オブジェクトの廃棄は最後に参照解除したスレッドで行われます。一斉に解放される場合、最後に参照されたスレッドが重くなります。 GCが専用スレッドで実装している場合は、もちろんGCスレッドが一手に 解放を担いますので、 その分一般のスレッドは軽くなります。 最後に参照解除したスレッドだからといって処理が重くなることは ありません。 各スレッドごとに独立したオブジェクトについては 実装方法の如何を問わず、生成したスレッドでしか削除できません。

79562
質問者

お礼

参照カウントではカウンタ0になったら自分自身を削除するそして、GCはカウンタが0になったらメモリの空き時間に削除される。 そして各々のスレッドが固有のオブジェクトしか持たないとき参照カウント同様スレッドも削除コードが動作する参照カウントはスレッドごとにオブジェクトの破壊が実行される。 各スレッド固有のオブジェクトは独自のスタック領域に配置されるので他のスレッドはアクセスできない。 スレッドごとにスタックは独立していますので、共有オブジェクトに配置できないのでヒープかstaticに配置させる。でよろしいのでしょうか?あと、一斉に解放される場合、なぜ最後に参照されたスレッドが重くなるのでしょうか?

79562
質問者

補足

"参照カウント"じゃなく"単純な実装"でした。

すると、全ての回答が全文表示されます。
  • BLK314
  • ベストアンサー率55% (84/152)
回答No.1

具体的な例を考えるとわかりやすいと思います。 今、A, B, C, D, E, Fという5つのオブジェクトがあるとします。 それらを組み合わせて Xというオブジェクトが成立しているものと考えます。 また、Xのインスタンスが1つだけあり、 X以外でA~Fのオブジェクトは利用されていないとします。 いま、Xが解体されるとします。 ウィキペディアでいう"単純な実装"とは 恐らく ガベージコレクション(GC)を備えず、 参照カウント=0になった時点で即deleteされる仕組をさすと思われます。 この方式では (X以外でA~Fは利用されていないので) X解体の時点でA~Fも同時に解体されます。 その為、解体処理全体が重くなる。 このことを指して "処理が遅くなる" と言っているのではないでしょうか? それに反し GCを備えたシステムであれば Xを解体する時点では A~Fは"不要になりました"とマーキングされるだけで、 実際にA~Fの解体は次のCPUアイドル時まで延期されます。 よって、Xの解体そのものは処理が軽くなります。 この比較をしているのではないでしょうか?

79562
質問者

お礼

わかりやすい説明ありがとうございます。ではこの考え方でよろしいのですね?回答ありがとうございました。

79562
質問者

補足

あともう1つ、一斉に開放する質問について、スレッド毎にギュウギュウ詰めされて重くなるということでよろしいのでしょうか?

すると、全ての回答が全文表示されます。

関連するQ&A