1.10 参照カウント法

C や C++のような言語では、プログラマはヒープ上のメモリを 動的に確保したり解放したりする責任があります。 こうした作業は C では関数malloc()free() で 行います。C++では本質的に同じ意味で演算子newdelete が使われます。そこで、以下の議論は C の場合に限定 して行います。

malloc() が確保する全てのメモリブロックは、最終的には free() を厳密に一度だけ呼び出して利用可能メモリのプールに 戻さねばなりません。そこで、適切な時にfree() を呼び出す ことが重要になります。 あるメモリブロックに対して、free() を呼ばなかったにも かかわらずそのアドレスを忘却してしまうと、ブロックが占有しているメモリ はプログラムが終了するまで再利用できなくなります。 これはメモリリーク(memory leak) と呼ばれています。 逆に、プログラムがあるメモリブロックに対してfree() を 呼んでおきながら、そのブロックを使い続けようとすると、 別の malloc() 呼び出しによって行われるブロックの再利用 と衝突を起こします。これは解放済みメモリの使用 (using freed memory) と呼ばれます。これは初期化されていないデータに対する参照と同様の よくない結果 -- コアダンプ、誤った参照、不可解なクラッシュ -- を引き起こします。

よくあるメモリリークの原因はコード中の普通でない処理経路です。 例えば、ある関数があるメモリブロックを確保し、何らかの計算を行って、 再度ブロックを解放するとします。さて、関数の要求仕様を変更して、 計算に対するテストを追加すると、エラー条件を検出し、関数の途中で 処理を戻すようになるかもしれません。 この途中での終了が起きるとき、確保されたメモリブロックは解放し忘れ やすいのです。コードが後で追加された場合には特にそうです。 このようなメモリリークが一旦紛れ込んでしまうと、長い間検出 されないままになることがよくあります: エラーによる関数の終了は、 全ての関数呼び出しのに対してほんのわずかな割合しか起きず、その一方で ほとんどの近代的な計算機は相当量の仮想記憶を持っているため、 メモリリークが明らかになるのは、長い間動作していたプロセスが リークを起こす関数を何度も使った場合に限られるからです。 従って、この種のエラーを最小限にとどめるようなコーディング規約や戦略を 設けて、不慮のメモリリークを避けることが重要なのです。

Python はmalloc()free() を非常によく利用 するため、メモリリークの防止に加え、解放されたメモリの使用を 防止する戦略が必要です。このために選ばれたのが 参照カウント法 (reference counting) と呼ばれる手法です。 参照カウント法の原理は簡単です: 全てのオブジェクトには カウンタがあり、オブジェクトに対する参照がどこかに保存されたら カウンタをインクリメントし、オブジェクトに対する参照が削除されたら デクリメントします。カウンタがゼロになったら、オブジェクトへの 最後の参照が削除されたことになり、オブジェクトは解放されます。

もう一つの戦略は自動ガベージコレクション (automatic garbage collection) と呼ばれています。 (参照カウント法はガベージコレクション戦略の一つとして挙げられることも あるので、二つを区別するために筆者は ``自動 (automatic)'' を使っています。) 自動ガベージコレクションの大きな利点は、ユーザがfree() を明示的によばなくてよいことにあります。 (速度やメモリの有効利用性も利点として主張されています -- が、 これは確たる事実ではありません。) C における自動ガベージコレクションの欠点は、真に可搬性のある ガベージコレクタが存在しないということです。それに対し、 参照カウント法は可搬性のある実装ができます (malloc()free() を利用できるのが前提です -- C 標準は これを保証しています)。 いつの日か、十分可搬性のあるガベージコレクタが C で使えるように なるかもしれませんが、それまでは参照カウント法でやっていく以外には ないのです。

Python では、伝統的な参照カウント法の実装を行っている一方で、 参照の循環を検出するために働く循環参照検出機構 (cycle detector) も提供しています。循環参照検出機構のおかげで、直接、間接に かかわらず循環参照の生成を気にせずにアプリケーションを構築できます; というのも、参照カウント法だけを使ったガベージコレクション実装に とって循環参照は弱点だからです。 循環参照は、(間接参照の場合も含めて) 相互への参照が入ったオブジェクト から形成されるため、循環内のオブジェクトは各々非ゼロの参照カウント を持ちます。典型的な参照カウント法の実装では、たとえ循環参照を形成する オブジェクトに対して他に全く参照がないとしても、 循環参照内のどのオブジェクトに属するメモリも再利用できません。

循環参照検出機構は、ごみとなった循環参照を検出し、Python で実装 された後始末関数 (finalizer、__del__() メソッド) が定義 されていないかぎり、それらのメモリを再利用できます。 後始末関数がある場合、検出機構は検出した循環参照を gc モジュール に (具体的にはこのモジュールの garbage 変数内) に公開します。gc モジュールではまた、 検出機構 (collect() 関数) を実行する方法や設定用の インタフェース、実行時に検出機構を無効化する機能も公開しています。 循環参照検出機構はオプションの機構とみなされています; デフォルトで入ってはいますが、Unix プラットフォーム (Mac OS X も含みます) ではビルド時にconfigure スクリプトの --without-cycle-gc オプションを使って、 他のプラットフォームではpyconfig.h ヘッダのWITH_CYCLE_GC 定義をはずして無効にできます。 こうして循環参照検出機構を無効化すると、gc モジュールは 利用できなくなります。



ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。