Pythonでのガベージコレクションについて学ぶ

この記事を読むのに約5分かかります。

プログラムを一度実行した後に終了すると、メモリ管理の重要性に気付かない場合があります。作成するプログラムを7x24時間継続して実行する必要がある場合、メモリ管理は非常に重要です。特に重要なサービスでは、メモリリークが発生しないようにする必要があります。

ここでのメモリリークは、メモリ内のデータの損失やメモリスペースの物理的な消失によるものではなく、プログラム自体が適切に設計されていないため、占有されているメモリが解放されても実際には解放されないため、システムで使用可能なメモリが深刻になります。不十分な場合、これが原因でシステムまたはサービスがクラッシュします。

Pythonはどのようにガベージコレクションを実行しますか?言い換えれば、Pythonは使用されなくなったメモリスペースをどのように再利用するのでしょうか。

**1、 再利用できるメモリを見つける方法は? **

ご存知のとおり、Pythonのすべてがオブジェクトであり、オブジェクトは一定量のメモリを占有します。変数を介してオブジェクトにアクセスします。変数の本質は、オブジェクトのポインタ(アドレス)です。

どのオブジェクトスペースを再利用するかを決定する方法は、そのような方法を考えるのは簡単です。オブジェクトを指す変数がない場合、それは役に立たないことを意味し、オブジェクトが占めるスペースを再利用できます。実際、Pythonはこれを行います。コードの一部と、その結果を見てみましょう。

import os
import psutil

# 現在のpythonプログラムが占有しているメモリサイズを表示します
def show_memory_info(hint):
 pid = os.getpid()
 p = psutil.Process(pid)
 info = p.memory_info()
 memory = info.rss /1024.0/1024print("{}メモリフットプリント: {} MB".format(hint, memory))

def func():show_memory_info("funcが呼び出される前")
 a =[i for i inrange(10000000)]show_memory_info("funcが呼び出される前")if __name__ =="__main__":func()show_memory_info("funcが呼び出された後")

結果は次のとおりです。

funcが呼び出される前のメモリ使用量:29.63671875 MB
func呼び出しが終了する前のメモリ使用量:414.1640625 MB
func呼び出し終了後のメモリ使用量:27.2265625 MB

この例では、関数funcで大きなリストaが作成された後、メモリ使用量がすぐに400 MBに増加し、func呼び出しの終了後にメモリが27 MBに復元されることがわかります。これは、func呼び出しの終了後、Pythonが変数aがもはや存在しないことを認識していることを示しています。使用するため、ガベージコレクションを行います。

別の見方をすれば、関数で宣言されたリストはローカル変数です。関数が戻った後、ローカル変数の参照はキャンセルされます。この時点で、リストaが指すオブジェクトへの参照の数は0であり、Pythonはガベージコレクションを実行します。 、したがって、以前に占有されていた大量のメモリが戻ってきました。

func関数の変数aをグローバル変数に変更すると、関数呼び出しが終了した後もaが使用され、この時点でメモリは再利用されません。

def func():show_memory_info("funcが呼び出される前")
global a
 a =[i for i inrange(10000000)]show_memory_info("funcが呼び出される前")

実行結果は次のとおりです。

funcが呼び出される前のメモリ使用量:29.625 MB
func呼び出しが終了する前のメモリ使用量:416.796875 MB
func呼び出し終了後のメモリ使用量:416.80078125 MB

つまり、変数の参照カウントが0の場合、Pythonインタープリターはそれを増やして再利用できます。したがって、問題は、オブジェクトへの参照の数をどのように判断するかということです。

幸い、Python標準ライブラリには、変数参照カウントを直接表示できる関数 sys.getrefcount(var)が用意されています。使用方法は以下のとおりです。

import sys

a =[]

# 2つの参照、1つはaから、もう1つはgetrefcountから
print(sys.getrefcount(a))

def func(a):
# 4つの参照、a、python関数呼び出しスタック、関数パラメーター、およびgetrefcount
print(sys.getrefcount(a))func(a)

# 2つの参照(1つはaから、もう1つはgetrefcountから)、関数func呼び出しは存在しなくなりました
print(sys.getrefcount(a))

出力

242

簡単な紹介として、sys.getrefcount()関数は、変数への参照の数を表示できます。このコード自体は理解しやすいはずですが、getrefcount自体もカウントを導入することを忘れないでください。

もう1つの注意点は、関数呼び出しが発生すると、2つの追加の参照があります。1つは関数スタックから、もう1つは関数パラメーターからです。

import sys

a =[]print(sys.getrefcount(a)) #2回

b = a

print(sys.getrefcount(a)) #3回

c = b
d = b
e = c
f = e
g = d

print(sys.getrefcount(a)) #8回

このコードを見ると、少し注意を払う必要があります。変数a、b、c、d、e、f、gはすべて同じオブジェクトを参照し、sys.getrefcount()関数はポインターをカウントしませんが、オブジェクトへの参照の数をカウントするため、最終的に合計8つの参照があります。

これで、Pythonが自動的にゴミを収集することがわかりました。

**2、 手動でメモリを再利用できますか? **

Pythonは自動的にメモリを再利用できますが、手動でメモリを再利用することをお勧めします。非常に簡単です。変数aがあり、それを再度使用したくない場合は、2つのコードを実行して取得します。

del a
gc.collect()

**3、 参照カウント0は、ガベージコレクションに必要かつ十分な条件ですか? **

誰かがそれを理解していると思わなければならないと思います。そして、インタビュアーがこの時点で尋ねた場合:0引用は、ゴミ収集を開始するために必要かつ十分な条件ですか?他の可能性はありますか?

あなたも閉じ込められても心配しないでください。小さな手順を実行して、最初にこの質問について考えることもできます。相互に参照し、他のオブジェクトによって参照されなくなった2つのオブジェクトがある場合、それらはガベージコレクションする必要がありますか?

import os,sys
import psutil

# 現在のpythonプログラムが占有しているメモリサイズを表示します
def show_memory_info(hint):
 pid = os.getpid()
 p = psutil.Process(pid)
 info = p.memory_info()
 memory = info.rss /1024.0/1024print("{}メモリフットプリント: {} MB".format(hint, memory))

def func2():show_memory_info("func2が呼び出される前")
 a =[i for i inrange(10000000)]
 b =[i for i inrange(10000000)]
 a.append(b)
 b.append(a)show_memory_info("通話終了前のfunc2")if __name__ =="__main__":func2()show_memory_info("func2が呼び出された後")

出力は次のとおりです。

func2が呼び出される前のメモリ使用量:29.65625 MB
func2呼び出しが終了する前のメモリ使用量:795.01953125 MB
func2が呼び出された後のメモリ使用量:795.09375 MB

ここで、aとbは相互に参照し、ローカル変数として、関数funcが呼び出された後、2つのポインターaとbはプログラムの意味で存在しなくなります。ただし、まだメモリ使用量があることは明らかです。どうして?相互参照のため、参照の数は0ではありません。

このコードが実稼働環境に表示された場合、最初はaとbが占めるスペースがそれほど大きくなくても、長時間実行すると、Pythonが占めるメモリは確実にどんどん大きくなり、最終的にはバーストすることを想像してみてください。サーバー、結果は悲惨です。

もちろん、お互いを引用するのは簡単だと言う人もいるかもしれませんが、それは大きな問題ではありません。ただし、より隠された状況は、参照ループの出現です。複雑なエンジニアリングコードの場合、参照ループを簡単に発見できない可能性があります。

参照リングの発生を本当に恐れてチェックアウトできない場合は、 gc.collect()を呼び出してゴミを収集し、上記のコードfunc2呼び出しの最後に gc.collect()を呼び出すことができます。

if __name__ =="__main__":func2()
 gc.collect()show_memory_info("func2が呼び出された後")

の結果

func2が呼び出される前のメモリ使用量:29.625 MB
func2呼び出しが終了する前のメモリ使用量:804.62109375 MB
func2が呼び出された後のメモリ使用量:30.54296875 MB

上記は手動コレクションのデモンストレーションです。実際、Pythonはそれを自動的に処理できます。Pythonはマークスイープアルゴリズムと世代別コレクションを使用して、循環参照の自動ガベージコレクションを有効にします。

最初にマークスイープアルゴリズムを見てみましょう。最初にグラフ理論を使用して、到達不能の概念を理解しましょう。有向グラフの場合、トラバーサルがノードから始まり、通過するすべてのノードがマークされている場合、トラバーサルが終了した後、マークされていないすべてのノードは到達不能ノードと呼ばれます。もちろん、これらのノードの存在は無意味ですが、当然、ゴミ収集する必要があります。もちろん、グラフ全体を毎回トラバースすることは、Pythonにとって大きなパフォーマンスの浪費です。したがって、Pythonのガベージコレクションの実装では、mark-sweepは二重にリンクされたリストを使用してデータ構造を維持し、コンテナオブジェクトのみを考慮します(コンテナオブジェクトのみが循環参照を生成できます)。ここでは特定のアルゴリズムについてはこれ以上説明しません。結局のところ、私たちの焦点はアプリケーションにあります。

世代別コレクションをもう一度見てみましょう。Pythonはすべてのオブジェクトを3つの世代に分割します。新しく作成されたオブジェクトは第0世代です。ガベージコレクションの後、まだ存在しているオブジェクトは前の世代から次の世代に順番に移動されます。世代ごとに自動ガベージコレクションを開始するためのしきい値は、個別に指定できます。ガベージコレクターで削除されたオブジェクトを差し引いた新しいオブジェクトが対応するしきい値に達すると、この世代のオブジェクトはガベージコレクションされます。実際、世代別収集は、新しいオブジェクトはゴミ収集される可能性が高く、存続期間が長いオブジェクトほど存続する可能性が高いという考えに基づいています。したがって、このアプローチにより、多くの計算を節約でき、それによってPythonのパフォーマンスが向上します。

あなたは今直面した質問に答えることができるはずです!はい、参照カウントは最も単純な実装の1つですが、参照カウントは必要かつ十分な条件ではなく、十分な非必須条件としてのみカウントできることを忘れないでください。他の可能性については、私たちが話している循環参照もその1つです。

**4、 メモリリークをデバッグする方法は? **

上記のブレスレットリファレンスのように、ツリーのような図で変数のリファレンス関係を表す方法はありますか?したがって、メモリリークをデバッグできます。実際、objgraphと呼ばれる、参照関係を視覚化するための非常に便利なパッケージがあります。このパッケージでは、主に2つの関数をお勧めします。1つ目は、明確な参照図を生成できる show_refs()です。

import objgraph

a =[1,2,3]
b =[4,5,6]

a.append(b)
b.append(a)

objgraph.show_refs([a])

objgraph.show_backrefs([a])

これらは、実行後にわかる参照関係をグラフィカルに表示します。これは、デバッグに非常に便利です。公式文書https://mg.pov.lt/objgraph/。

5、 総括する ##

1、 Pythonは自動的にガベージコレクションを実行します。 2.参照カウントが0の場合のリサイクルが最も単純なケースであり、循環参照があります。 3.Pythonには2つの自動リサイクルアルゴリズムがあります。

4、 メモリリークをデバッグするために、objgraphは優れた視覚分析ツールです。

(終了)

Recommended Posts

Pythonでのガベージコレクションについて学ぶ
Pythonガベージコレクションメカニズム
Python3のルーチンについて学ぶ
Pythonでの継承について話す
Pythonで文字列について話す
Pythonのモジュールについて話す
Pythonガベージコレクションメカニズムの詳細な分析
1分でPythonを学ぶ|オブジェクト指向(中国語)
1分でPythonを学ぶ| Python関数(オン)
pythonの関数
1分でPythonを学ぶ|オブジェクト指向(パート1)
03.Pythonエントリの演算子
Pythonの初心者はデコレータを学びます
Pythonの日常について話す
Pythonの結合関数
12.Python3でのネットワークプログラミング
pythonでステートメントを印刷する
Pythonでの同時リクエスト
Ubuntuにpythonをインストールする
Pythonでのコンテキスト管理
pythonの算術演算子
pythonでguiを書く
PythonでのMongoDBの使用
PythonのStr文字列
Pythonでの計算ジオメトリ
Pythonのハードコア操作を1分で学ぶ
Pythonでの同時リクエスト(パート2)
Python機能プログラミングについて話す
pythonをすばやく学ぶ方法
Python3.9の注目すべき更新ポイント
Pythonアプリケーションを3分でコンテナ化
Pythonインタビュー質問コレクション(3)
pythonのイントロスペクションとは何ですか
pythonのオブジェクト指向とは何ですか
Pythonのジェネレーターとイテレーター
Python共通モジュールのコレクション