web-dev-qa-db-ja.com

Pythonでメモリを解放する

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

  1. インタプリタで実行すると、

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

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

    del foo
    

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

  2. 特に50.5mbをリ​​リースするのはなぜですか-ベースのリリース量はいくらですか?

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

NOTEこの質問は Pythonでメモリを明示的に解放するにはどうすればよいですか? とは異なりますインタプリタがガベージコレクションを介してオブジェクトを解放した後(gc.collectを使用するかどうかにかかわらず)のベースラインからのメモリ使用量。

116
Jared

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

さらに、組み込み型は、小さなオブジェクトアロケーターを使用する場合と使用しない場合がある、以前に割り当てられたオブジェクトの空きリストを保持します。 int型は、独自のメモリが割り当てられた空きリストを保持し、クリアするにはPyInt_ClearFreeList()を呼び出す必要があります。これは、完全な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はリストを作成しません。したがって、上記のテストでは1,000万個のintオブジェクトは作成されません。たとえそれが行われたとしても、3.xのint型は基本的に2.x longであり、フリーリストを実装していません。

80
eryksun

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

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

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

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

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

第一に、子プロセスを作成する最も簡単な方法は concurrent.futures (または3.1以前では、PyPIで futures バックポート)を使用することです:

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

もう少し制御が必要な場合は、 multiprocessing モジュールを使用します。

費用は次のとおりです。

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

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

特に50.5mbをリリースするのはなぜですか-ベースでリリースされる量はいくらですか?

最終的には、Pythonとmalloc内の一連の偶然の一致が非常に困難です。

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

または、使用中のページを測定している可能性があります。これは、割り当てられているが決して触れられていないページ(Linuxのように楽観的に過剰に割り当てられているシステム)、割り当てられているがMADV_FREEなどのページなどをカウントする場合とカウントしない場合があります.

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

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

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

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

doオブジェクトの割り当てを解除する場合、これにより次のレベルでオブジェクトストレージのブロックの割り当てが解除されるかどうかを知るには、オブジェクトアロケータの内部状態も知る必要があります実装方法として。 (ブロック内の最後のものの割り当てを解除しない限り、それは明らかに起こり得ません。それでも、それは起こらないかもしれません。)

オブジェクトストレージのブロックをdo割り当て解除すると、これがfree呼び出しを引き起こすかどうかを知るために、PyMemアロケーターの内部状態とその実装方法を知る必要があります。 (繰り返しますが、malloced領域内の最後の使用中ブロックの割り当てを解除する必要がありますが、それでも発生しない場合があります。)

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

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

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

リソースに制限のある(埋め込みなどの)開発を行っていない限り、これらの詳細を気にする必要はありません。

そして、もしあなたがリソース限定開発をしているなら、これらの詳細を知ることは役に立たない。これらのすべてのレベル、特にアプリケーションレベルで必要なメモリ(特に、間に1つのシンプルで十分に理解されたアプリケーション固有のゾーンアロケーターを使用)をエンドランする必要があります。

28
abarnert

まず、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」ターミナルで、システムでメモリがどのように管理されているかを観察します。

がんばろう!

追伸DebianまたはUbuntuシステムで作業していると思います

1
de20ce