Pythonでのメモリの解放


128

次の例では、メモリの使用に関していくつか質問があります。

  1. 通訳で走れば

    foo = ['bar' for _ in xrange(10000000)]

    私のマシンで使用されている実際のメモリはに達します80.9mb。そして私は・・・それから私は、

    del foo

    実メモリーはダウンしますが、のみ30.4mbです。インタプリタは4.4mbベースラインを使用するので26mb、OSにメモリを解放しないことの利点は何ですか?それは、Pythonが「前もって計画を立てている」ためであり、それだけ多くのメモリを再び使用する可能性があると考えていますか?

  2. なぜそれ50.5mbが特に放出されるのですか- 放出される量は何に基づいていますか?

  3. 使用されたすべてのメモリをPythonに強制的に解放させる方法はありますか(それ以上メモリを使用しないことがわかっている場合)?

この質問は、Pythonでメモリを明示的に解放する方法とは異なりますか? この質問は、インタプリタがガベージコレクションを介してオブジェクトを解放した後でも(使用のgc.collect有無にかかわらず)、ベースラインからのメモリ使用量の増加を主に扱っているためです。


4
この動作はPythonに固有のものではないことに注意してください。一般に、プロセスがヒープに割り当てられたメモリを解放すると、メモリはプロセスが終了するまでOSに解放されません。
NPE 2013年

あなたの質問は複数のことを尋ねます—それらのいくつかは重複です、それらのいくつかはSOには不適切です、そしていくつかは良い質問であるかもしれません。Pythonがメモリを解放しないのか、正確にはどのような状況でできる/できないのか、根本的なメカニズムは何か、なぜそのように設計されたのか、回避策があるのか​​、それとも完全に別の何かなのかを尋ねていますか?
abarnert 2013年

2
@abarnert類似したサブ質問を組み合わせました。あなたの質問に答えるために:私はPythonがOSにある程度のメモリを解放することを知っていますが、なぜそれがすべてではなく、なぜそれが行う量なのですか?それができない状況がある場合、なぜですか?どのような回避策も。
Jared


@jwwそうは思いません。この質問は、への呼び出しでガベージを完全に収集した後でも、インタプリタプロセスがメモリを解放しない理由に本当に関連していgc.collectます。
Jared

回答:


86

ヒープに割り当てられたメモリは、最高水準点の影響を受ける可能性があります。これはPyObject_Malloc、8バイトの倍数-最大256バイト(3.3では512バイト)の割り当てサイズに分類された、4 KiBプールに小さなオブジェクト()を割り当てるためのPythonの内部最適化によって複雑になっています。プール自体は256 KiBアリーナにあるため、1つのプール内の1つのブロックだけが使用される場合、256 KiBアリーナ全体は解放されません。Python 3.3では、小さなオブジェクトアロケータがヒープの代わりに匿名のメモリマップを使用するように切り替えられたため、メモリの解放でパフォーマンスが向上するはずです。

さらに、組み込み型は、スモールオブジェクトアロケータを使用する場合と使用しない場合がある、以前に割り当てられたオブジェクトのフリーリストを維持します。intタイプは、独自の割り当てられたメモリに空きリストを維持し、それをクリアすると、呼び出しが必要ですPyInt_ClearFreeList()。これは、fullを実行することで間接的に呼び出すことができますgc.collect

このように試して、何が得られるか教えてください。これがpsutil.Process.memory_infoのリンクです。

import os
import gc
import psutil

proc = psutil.Process(os.getpid())
gc.collect()
mem0 = proc.get_memory_info().rss

# create approx. 10**7 int objects and pointers
foo = ['abc' for x in range(10**7)]
mem1 = proc.get_memory_info().rss

# unreference, including x == 9999999
del foo, x
mem2 = proc.get_memory_info().rss

# collect() calls PyInt_ClearFreeList()
# or use ctypes: pythonapi.PyInt_ClearFreeList()
gc.collect()
mem3 = proc.get_memory_info().rss

pd = lambda x2, x1: 100.0 * (x2 - x1) / mem0
print "Allocation: %0.2f%%" % pd(mem1, mem0)
print "Unreference: %0.2f%%" % pd(mem2, mem1)
print "Collect: %0.2f%%" % pd(mem3, mem2)
print "Overall: %0.2f%%" % pd(mem3, mem0)

出力:

Allocation: 3034.36%
Unreference: -752.39%
Collect: -2279.74%
Overall: 2.23%

編集:

システム内の他のプロセスの影響を排除するために、プロセスVMサイズに関連する測定に切り替えました。

Cランタイム(glibc、msvcrtなど)は、上部の連続する空き領域が一定の動的な、または構成可能なしきい値に達すると、ヒープを縮小します。glibcでは、これをmallopt(M_TRIM_THRESHOLD)で調整できます。このことを考えると、ヒープがあなたのブロックよりも(さらにはさらに)縮小したとしても、驚くことではありませんfree

3.x rangeではリストを作成しないため、上記のテストでは1000万のintオブジェクトが作成されません。たとえそうであってもint、3.x の型は基本的に2.x longであり、フリーリストを実装していません。


使用memory_info()の代わりに、get_memory_info()x定義されている
アジズアルト

intPython 3でも10 ^ 7 秒は得られますが、それぞれがループ変数の最後を置き換えるため、一度に
デイビスヘリング2018

私はメモリリークの問題に遭遇しました、そしてあなたがここで答えたのはその理由だと思います。しかし、どうすれば私の推測を証明できますか?多くのプールがmallocされていることを示すことができるツールはありますか?
ruiruige1991

130

ここで本当に気になる質問は次のとおりです。

使用されたすべてのメモリをPythonに強制的に解放させる方法はありますか(それ以上メモリを使用しないことがわかっている場合)?

いいえ、ありません。しかし、簡単な回避策があります。子プロセスです。

500MBの一時ストレージが5分間必要であるが、その後さらに2時間実行する必要があり、それ以上多くのメモリに触れない場合は、メモリを集中的に使用する子プロセスを生成します。子プロセスがなくなると、メモリが解放されます。

これは完全に簡単で無料ではありませんが、それはかなり簡単で安価であり、通常は取引に価値があるのに十分です。

まず、子プロセスを作成する最も簡単な方法はconcurrent.futures(または3.1以前の場合はfuturesPyPI のバックポート)を使用することです。

with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
    result = executor.submit(func, *args, **kwargs).result()

もう少し制御が必要な場合は、multiprocessingモジュールを使用してください。

費用は次のとおりです。

  • 一部のプラットフォーム、特にWindowsでは、プロセスの起動が少し遅い。ここでは分単位ではなくミリ秒単位で話しているので、1人の子供を回転させて300秒相当の作業を行っている場合は、それに気づくことすらありません。しかし、それは無料ではありません。
  • 使用する大量の一時メモリが本当に大きい場合、これを行うと、メインプログラムがスワップアウトされる可能性があります。もちろん、長期的には時間を節約できます。そのメモリが永遠に残っていると、ある時点でスワッピングが発生するからです。ただし、一部のユースケースでは、これにより段階的な速度低下が非常に顕著なオールアットワンス(および早期)遅延に変わる可能性があります。
  • プロセス間での大量のデータの送信が遅くなる可能性があります。繰り返しになりますが、2Kを超える引数を送信し、64Kの結果を返すことについて話している場合は、それに気付くことさえありませんが、大量のデータを送受信している場合は、他のメカニズムを使用する必要があります(mmapPEDなどのファイル、;の共有メモリAPI multiprocessingなど)。
  • プロセス間で大量のデータを送信することは、データをピクル可能にする必要があることを意味します(または、ファイルまたは共有メモリにそれらを貼り付ける場合は、struct-ableまたは理想的にはctypes-able)。

問題を解決していませんが、本当に素晴らしいトリック:(しかし、私はそれが本当に好きです
ddofborg

32

eryksunが質問#1に回答し、質問#3(元の#4)に回答しましたが、今度は質問#2に回答しましょう。

特に50.5mbをリリースするのはなぜですか?リリースされる量は何に基づいていますか?

それが基づいているのは、結局のところ、Python内部での一連の偶然の一致であり、mallocこれは予測が非常に困難です。

まず、メモリの測定方法によっては、実際にメモリにマッピングされているページのみを測定している場合があります。その場合、ページャーによってページがスワップアウトされると、メモリは解放されていなくても「解放された」と表示されます。

または、割り当てられているが変更されていないページ(Linuxのように楽観的に過剰割り当てされているシステム)、割り当てられているがタグ付けされているページMADV_FREEなどをカウントする場合とカウントしない場合がある使用中のページを測定している場合があります。

割り当てられたページを実際に測定していて(実際にはそれほど有用なことではありませんが、それはあなたが求めていることのようです)、ページが実際に割り当て解除された場合、これが発生する可能性のある2つの状況は次のとおりです。brkデータセグメントを縮小するために使用または同等のもの(最近では非常にまれです)、またはmunmapマッピングされたセグメントを解放するために使用または類似しています。(また、理論的には、マッピングされたセグメントの一部を解放する方法があるなど、後者のマイナーバリアントもあります。たとえば、すぐにマップ解除MAP_FIXEDしたMADV_FREEセグメントのためにそれを盗みます。)

しかし、ほとんどのプログラムは、メモリページから直接物事を割り当てません。それらは- mallocスタイルのアロケーターを使用します。を呼び出すfreeと、アロケータはfree、マッピング(またはデータセグメントの最後のNページ)の最後のライブオブジェクトをたまたま使用している場合にのみ、OSにページを解放できます。アプリケーションがこれを合理的に予測したり、事前に発生したことを検出したりすることはできません。

CPythonのは、これはさらに複雑-それはの上にカスタムメモリアロケータの上にカスタム2レベルのオブジェクトアロケータを持っていますmalloc。(さらに詳しい説明については、ソースのコメントを参照してください。)さらに、C APIレベルでさえ、Pythonははるかに少なく、トップレベルのオブジェクトが割り当て解除されるタイミングを直接制御することすらできません。

では、オブジェクトを解放するときに、それがOSにメモリを解放するかどうかをどのようにして知るのでしょうか。まあ、最初に、最後の参照(知らなかった内部参照を含む)を解放したことを知って、GCが割り当てを解除できるようにする必要があります。(他の実装とは異なり、少なくともCPythonは許可されたらすぐにオブジェクトの割り当てを解除します。)これは通常、次のレベルで少なくとも2つのものの割り当てを解除します(たとえば、文字列の場合、PyStringオブジェクトと文字列バッファーを解放します) )。

あなたがいる場合行う、これはオブジェクトストレージのブロックを解放する次のレベルのダウンを引き起こすかどうかを知るために、オブジェクトを解放し、あなたはそれを実現していますかだけでなく、オブジェクトアロケータの内部状態を知っている必要があります。(ブロックの最後の割り当てを解除しない限り、明らかに発生することはありません。それでも発生しない場合があります。)

あなたがいる場合行う、これが原因かどうかを知るために、オブジェクトストレージのブロックの割り当てを解除するfreeコールを、あなたはそれが実装されますかだけでなく、PyMemアロケータの内部状態を知っている必要があります。(繰り返しますが、malloced領域内の使用中の最後のブロックの割り当てを解除する必要がありますが、それでも発生しない場合があります。)

edリージョンを実行 freeする場合malloc、これがmunmapや同等の(またはbrk)を引き起こすかどうかを知るには、の内部状態と、そのmalloc実装方法を知る必要があります。そして、これは他とは異なり、プラットフォーム固有のものです。(そして繰り返しますが、通常mallocmmapセグメント内で使用中の最後の割り当てを解除する必要がありますが、それでも発生しない場合があります。)

それで、なぜそれがちょうど50.5mbをリリースしたのかを理解したいのであれば、それをボトムアップでトレースする必要があります。mallocこれらの1つ以上のfree呼び出しを行ったときに50.5mb相当のページのマップを解除したのはなぜですか(おそらく50.5mbより少し多い)。プラットフォームのを読みmalloc、さまざまなテーブルやリストを調べて現在の状態を確認する必要があります。(一部のプラットフォームでは、システムレベルの情報を利用することもあります。オフラインで検査するシステムのスナップショットを作成せずにキャプチャすることはほとんど不可能ですが、幸い、これは通常問題ではありません。)そして、その上の3つのレベルで同じことを行います。

したがって、質問に対する唯一の有用な答えは「理由」です。

リソースが限定された(組み込みなど)開発を行わない限り、これらの詳細を気にする必要はありません。

あなたがいる場合とされているリソースが制限された開発を行って、これらの詳細を知ることは無用です。これらのすべてのレベル、特にmmapアプリケーションレベルで必要なメモリ(おそらく、間に単純な、よく理解されているアプリケーション固有のゾーンアロケータが1つある場合)をエンドランする必要があります。


2

最初に、glanceをインストールすることができます。

sudo apt-get install python-pip build-essential python-dev lm-sensors 
sudo pip install psutil logutils bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard
sudo pip install glances

次に、ターミナルで実行してください!

glances

Pythonコードで、ファイルの先頭に次を追加します。

import os
import gc # Garbage Collector

メモリを解放する "Big"変数(例:myBigVar)を使用した後、Pythonコードに次のように記述します。

del myBigVar
gc.collect()

別のターミナルでpythonコードを実行し、「glances」ターミナルで、システムでメモリがどのように管理されているかを確認します。

幸運を!

PS私はあなたがDebianまたはUbuntuシステムで作業していると思います

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.