Python Language
ガベージコレクション
サーチ…
備考
Pythonのガベージコレクタ(3.5以降)は、その核となる単純な参照カウント実装です。オブジェクト(たとえばa = myobject
)への参照を作成するたびに、そのオブジェクト( a = myobject
)の参照カウントがインクリメントされます。参照が削除されるたびに、参照カウントが減少し、参照カウントが0
に達すると、そのオブジェクトへの参照を保持するものは何もないことが分かり、解放することができます。
Pythonのメモリ管理がどのように動作するかについての一般的な誤解の1つは、 del
キーワードはオブジェクトのメモリを解放することです。本当じゃない。実際には、 del
キーワードは単にオブジェクトrefcountを減らすだけです。つまり、refcountが0に達するのに十分な時間を呼び出すと、オブジェクト内でガベージコレクションが行われる可能性があります。 )。
Pythonはオブジェクトを初めて必要とするときに積極的にオブジェクトを作成またはクリーンアップします。割り当てを実行するとオブジェクトのメモリはその時点で割り当てられます(cpythonは特定のタイプのオブジェクトを再利用することがあります。ほとんどの場合、空きオブジェクトプールを保持せず、必要なときに割り当てを実行します)。同様に、refcountが0にデクリメントされると、GCはそれをクリーンアップします。
世代別ガベージコレクション
1960年代にJohn McCarthyは、Lispで使用されているrefcountingアルゴリズムを実装したときに、ガベージコレクションを参照カウントする際に致命的な欠陥を発見しました:2つのオブジェクトが循環参照でお互いを参照するとどうなりますか?彼らがいつもお互いを参照する場合、外部参照がなくても、どうしてこれら2つのオブジェクトをガベージコレクションすることができますか?この問題は、リングバッファまたは二重リンクリストの任意の2つの連続したエントリなど、任意の循環データ構造にも及ぶ。 Pythonはこの問題を、 世代別ガベージコレクションと呼ばれる別のガベージコレクションアルゴリズムで少し面白いように修正しようとします。
本質的に、Pythonでオブジェクトを作成するときには、そのオブジェクトを二重リンクリストの最後に追加します。時にはPythonがこのリストをループして、リスト内のオブジェクトがどのオブジェクトを参照しているかをチェックし、それらがリストにも含まれているかどうかをチェックします(なぜそれらが一瞬でないのか分かります)。現時点では、物事がいつ動くかを決めるヒューリスティックがありますが、物事を単純にするために単一のコレクションの後にあると仮定しようとします。0より大きいrefcountを持つものは、 "Generation 1" (これは、すべてのオブジェクトが常に世代0のリストにあるわけではないためです)、このループはあまり頻繁に適用されません。これは、世代別ガベージコレクションが入る場所です。デフォルトでは3つの世代がPythonにあります(オブジェクトの3つのリンクされたリスト)。最初のリスト(世代0)にはすべての新しいオブジェクトが含まれます。 GCサイクルが発生してオブジェクトが収集されない場合、それらは第2のリスト(第1世代)に移動し、第2のリストでGCサイクルが発生し、それらがまだ収集されない場合、第3のリスト(世代2 )。第3世代のリスト(「第2世代」と呼ばれているのは、ゼロインデックス処理なので)は、最初の2つよりもはるかに少ない頻度でガベージコレクションされます。オブジェクトが長生きするとGCedになる可能性は低く、アプリケーションの存続期間中にGCされるので、1回のGC実行ごとに時間を浪費することはありません。さらに、ほとんどのオブジェクトは比較的早くガベージ・コレクションされることがわかりました。これからは、若いうちに亡くなるので、これらの「良いもの」と呼ぶことにします。これは「弱世代仮説」と呼ばれ、60年代に初めて観察されたものです。
早急に:最初の2世代と異なり、長寿命の第3世代のリストは定期的なスケジュールでガベージコレクションされません。保留中の長いオブジェクト(第3世代のリストにあるものの、実際にはGCサイクルをまだ持っていないオブジェクト)と、リスト内の長命オブジェクトの合計の比率が25%を超えたときにチェックされます。これは、3番目のリストは無制限であるため(実際には別のリストに移動されることはないため、実際にガベージコレクションされたときにのみ消えてしまいます)、長寿命のオブジェクトをたくさん作成するアプリケーションの場合、GCサイクル3番目のリストにはかなり長い時間がかかることがあります。比率を使用することによって、「オブジェクトの総数における償却された線形性能」を達成する。リストが長くなればなるほどGCは長くなりますが、GCを実行する頻度は少なくなります(ここでは、Martin VonLöwisによるこのヒューリスティックの最初の提案が続きます)。第3世代または「成熟した」リスト上でガベージコレクションを実行する行為は、「完全ガベージコレクション」と呼ばれます。
だから世代別ガーベジコレクションは、GCを必要としそうもないオブジェクトをスキャンする必要がないため、時間を浪費しますが、循環参照を破るのにどのように役立ちますか?おそらくそれほどうまくいかないかもしれません。これらの参照サイクルを実際に破る関数は次のようになります 。
/* Break reference cycles by clearing the containers involved. This is
* tricky business as the lists can be changing and we don't know which
* objects may be freed. It is possible I screwed something up here.
*/
static void
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
世代別ガベージコレクションがこれを助ける理由は、リストの長さを別々のカウントとして保持できることです。新しいオブジェクトを世代に追加するたびに、このカウントを増やし、オブジェクトを別の世代に移動するか、またはオブジェクトを別の世代に移動するか、それをデアロックします。理論的には、GCサイクルの終わりに、このカウント(最初の2世代の場合)は常に0にする必要があります。そうでない場合は、リスト内の何かが循環参照の何らかの形であり、取り除くことができます。しかし、もう一つの問題があります。残ったオブジェクトにPythonの魔法メソッド__del__
があるとどうなりますか? __del__
は、Pythonオブジェクトが破棄される__del__
に呼び出されます。循環参照で2つのオブジェクトが持っている場合は、 __del__
メソッドを、私たちは1を破壊すると、他の人を壊さないことを確認することはできません__del__
メソッドを。考案された例として、次のように書いたと想像してみてください。
class A(object):
def __init__(self, b=None):
self.b = b
def __del__(self):
print("We're deleting an instance of A containing:", self.b)
class B(object):
def __init__(self, a=None):
self.a = a
def __del__(self):
print("We're deleting an instance of B containing:", self.a)
AのインスタンスとBのインスタンスを互いにポイントするように設定し、それらは同じガベージコレクションサイクルに終わるでしょうか?私たちがランダムに1つを選択し、最初にAのインスタンスをdeallocしたとしましょう。 Aの__del__
メソッドが呼び出され、それが出力され、Aが解放されます。次にBに来て、 __del__
メソッドと呼んで、おっと! Segfault! Aは存在しません。この問題を解決するには、まず__del__
メソッドを残して__del__
、次にすべてを実際に無効にする別のパスを呼び出すことでこれを修正することができます。しかし、これは別の問題を導入します:1つのオブジェクト__del__
メソッドがGCedしようとしている他のどこかで私たちを参照していますか?まだ参照サイクルがありますが、実際にはどちらのオブジェクトもGCが使用されていなくても実際にGCすることはできません。オブジェクトが循環データ構造の一部でなくても、それ自身の__del__
メソッドで復活できることに注意してください。 Pythonはこれをチェックし、 __del__
メソッドが呼び出された後にオブジェクトrefcountが増加した場合にGCingを停止します。
これをCPythonで処理するには、 __del__
なゴミのグローバルリストにそれらのGC __del__
なオブジェクト(何らかの形式の循環参照と__del__
メソッドを持つもの)を貼り付けて、それを永遠に残すことです。
/* list of uncollectable objects */
static PyObject *garbage = NULL;
参照カウント
Pythonのメモリ管理の大部分は、参照カウントで処理されます。
オブジェクトが参照されるたびに(例えば、変数に代入される)、参照カウントは自動的に増加します。逆参照されると(例えば、変数が範囲外になる)、参照カウントは自動的に減少します。
参照カウントがゼロになると、オブジェクトは直ちに破棄され、メモリはただちに解放されます。したがって、ほとんどの場合、ガベージコレクタは必要ない。
>>> import gc; gc.disable() # disable garbage collector
>>> class Track:
def __init__(self):
print("Initialized")
def __del__(self):
print("Destructed")
>>> def foo():
Track()
# destructed immediately since no longer has any references
print("---")
t = Track()
# variable is referenced, so it's not destructed yet
print("---")
# variable is destructed when function exits
>>> foo()
Initialized
Destructed
---
Initialized
---
Destructed
参照の概念をさらに実証するために:
>>> def bar():
return Track()
>>> t = bar()
Initialized
>>> another_t = t # assign another reference
>>> print("...")
...
>>> t = None # not destructed yet - another_t still refers to it
>>> another_t = None # final reference gone, object is destructed
Destructed
参照サイクル用ガベージコレクタ
ガベージコレクタが必要な唯一の時間は、 参照サイクルがある場合です。参照サイクルのシンプル例は、AがBを参照し、BがAを参照し、AまたはBのいずれも参照しないものです。AまたはBのいずれもプログラムのどこからでもアクセスできないため、安全に破棄できます。それらの参照カウントは1であるため、参照カウントアルゴリズムだけでは解放することはできません。
>>> import gc; gc.disable() # disable garbage collector
>>> class Track:
def __init__(self):
print("Initialized")
def __del__(self):
print("Destructed")
>>> A = Track()
Initialized
>>> B = Track()
Initialized
>>> A.other = B
>>> B.other = A
>>> del A; del B # objects are not destructed due to reference cycle
>>> gc.collect() # trigger collection
Destructed
Destructed
4
基準サイクルは任意に長くすることができます。もしAがBを指してCを指しているならば、Aを指すZを指しているならば、ガーベージコレクションフェーズまではAからZのいずれも収集されません。
>>> objs = [Track() for _ in range(10)]
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
>>> for i in range(len(objs)-1):
... objs[i].other = objs[i + 1]
...
>>> objs[-1].other = objs[0] # complete the cycle
>>> del objs # no one can refer to objs now - still not destructed
>>> gc.collect()
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
20
delコマンドの効果
del v
を使用してスコープから変数名を削除するか、またはdel v[item]
またはdel[i:j]
を使用してコレクションからオブジェクトを削除するか、またはdel v.name
を使用して属性を削除するか、オブジェクトは、デストラクタ呼び出しや解放されたメモリをトリガしません 。オブジェクトは、参照カウントが0になったときにのみ破棄されます。
>>> import gc
>>> gc.disable() # disable garbage collector
>>> class Track:
def __init__(self):
print("Initialized")
def __del__(self):
print("Destructed")
>>> def bar():
return Track()
>>> t = bar()
Initialized
>>> another_t = t # assign another reference
>>> print("...")
...
>>> del t # not destructed yet - another_t still refers to it
>>> del another_t # final reference gone, object is destructed
Destructed
プリミティブオブジェクトの再利用
アプリケーションの最適化に役立つ興味深い点は、プリミティブが実際にはフードの下でも参照されることです。数字を見てみましょう。 -5と256の間のすべての整数に対して、Pythonは常に同じオブジェクトを再利用します:
>>> import sys
>>> sys.getrefcount(1)
797
>>> a = 1
>>> b = 1
>>> sys.getrefcount(1)
799
refcountが増加することに注意してください。つまり、 a
とb
は1
プリミティブを参照するときに同じ基本オブジェクトを参照します。しかし、より大きい数値の場合、Pythonは実際には基礎となるオブジェクトを再利用しません。
>>> a = 999999999
>>> sys.getrefcount(999999999)
3
>>> b = 999999999
>>> sys.getrefcount(999999999)
3
999999999
のrefcountはa
とb
に代入するときに変更されないため、2つの異なる基本オブジェクトを参照することができます。ただし、両方に同じプリミティブが割り当てられています。
オブジェクトの参照数を表示する
>>> import sys
>>> a = object()
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b
>>> sys.getrefcount(a)
2
強制的にオブジェクトの割り当てを解除する
Python 2と3の両方でrefcountが0でなくても、オブジェクトの割り当てを強制的に解除することができます。
どちらのバージョンもctypes
モジュールを使用しています。
警告:これを行うと 、Python環境が不安定になり、トレースバックなしでクラッシュする傾向があります。この方法を使用すると、セキュリティ上の問題が発生する可能性があります(まったく考えられません)。再度参照しないことが確実なオブジェクトのみを割り当て解除してください。これまで
import ctypes
deallocated = 12345
ctypes.pythonapi._Py_Dealloc(ctypes.py_object(deallocated))
import ctypes, sys
deallocated = 12345
(ctypes.c_char * sys.getsizeof(deallocated)).from_address(id(deallocated))[:4] = '\x00' * 4
実行後、現在割り当て解除されているオブジェクトへの参照があると、Pythonは未定義の動作を引き起こすか、トレースバックなしでクラッシュします。ガベージコレクタがそのオブジェクトを削除しなかった理由はおそらくありました...
None
を割り当て解除すると、特別なメッセージが表示されます。 Fatal Python error: deallocating None
。クラッシュする前にFatal Python error: deallocating None
します。
ガベージコレクションの管理
メモリクリーンアップが実行されるときに影響を与える2つのアプローチがあります。それらは、自動プロセスが実行される頻度と手動でクリーンアップをトリガーする頻度に影響します。
ガベージコレクタは、コレクタが動作する頻度に影響を与えるコレクタしきい値を調整することによって操作できます。 Pythonは、世代別のメモリ管理システムを使用しています。新しいオブジェクトは最新の世代に保存されます - 世代 0および各生き残ったコレクションでは、オブジェクトは古い世代に昇格します。最後の世代に達した後- generation2を 、彼らが推進されなくなりました。
しきい値は次のスニペットを使用して変更できます。
import gc
gc.set_threshold(1000, 100, 10) # Values are just for demonstration purpose
最初の引数は、 世代交代のためのしきい値を表します。 割り振りの数が割り振り解除の数を1000を超えるたびに、ガベージコレクターが呼び出されます。
プロセスを最適化するために、古い世代は各実行時にはクリーニングされません。 2番目と3番目の引数はオプションで、古い世代のクリーニング頻度を制御します。 generation0が generation1を掃除せずに100回を加工した場合には、generation1が処理されます。同様に、generation2のオブジェクトはgeneration1におけるものがgeneration2に触れることなく10回洗浄した場合にのみ処理されます。
しきい値を手動で設定するのが有益なのは、プログラムが割り当てを解除せずに多数の小さなオブジェクトを割り当てて、ガベージコレクタが頻繁に実行される(各generation0_thresholdオブジェクトの割り当て)場合です。コレクターは非常に高速ですが、膨大な数のオブジェクトで動作する場合、パフォーマンスの問題が発生します。とにかく、しきい値を選択するためのすべての戦略に適合するサイズはなく、ユースケースは信頼できるものです。
手動でコレクションをトリガするには、次のスニペットのようにします。
import gc
gc.collect()
ガーベッジ・コレクションは、消費または使用可能なメモリーではなく、割り振りおよび割り振り解除の数に基づいて自動的にトリガーされます。したがって、大きなオブジェクトを使用して作業する場合、自動クリーンアップがトリガーされる前にメモリがなくなる可能性があります。これはガベージコレクタを手動で呼び出すための良いユースケースです。
それは可能ですが、それは奨励された練習ではありません。メモリリークを避けることが最善の選択肢です。とにかく、大きなプロジェクトではメモリリークを検出することができますが、ガベージコレクションを手動でトリガすることは、さらにデバッグするまでは迅速な解決策として使用できます。
長期実行プログラムの場合、ガベージコレクションは時間ベースまたはイベントベースでトリガーできます。最初の例は、一定数の要求の後に収集をトリガーするWebサーバーです。後では、特定のタイプの要求が受信されたときにガベージコレクションをトリガーするWebサーバー。
ガベージコレクションがクリーンアップするのを待つ必要はありません
ガベージコレクションがクリーンアップされるという事実は、ガベージコレクションサイクルがクリーンアップするのを待つべきではありません。
特に、ガベージコレクションがファイルハンドル、データベース接続、およびオープンネットワーク接続を閉じるのを待つべきではありません。
例えば:
次のコードでは、fがファイルへの最後の参照であった場合、ファイルが次のガベージコレクションサイクルで閉じられることを前提としています。
>>> f = open("test.txt")
>>> del f
もっと明確な方法は、 f.close()
を呼び出すことf.close()
。 コンテキストマネージャとも呼ばれるwith
ステートメントを使用することで、より洗練された操作を行うことができます。
>>> with open("test.txt") as f:
... pass
... # do something with f
>>> #now the f object still exists, but it is closed
with
ステートメントを使用すると、開いているファイルの下にコードをインデントすることができます。これにより、ファイルのオープン期間を明示的に簡単に確認できます。 while
ブロックで例外が発生しても、常にファイルをクローズします。