2013-03-16 23 views
94

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

  1. 私は通訳に実行する場合、私のマシン上で使用

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

    実メモリは80.9mbに上がります。次に、

    del foo 
    

    実際のメモリは、30.4mbにしかなりません。通訳者は4.4mbベースラインを使用していますので、メモリに26mbをOSに公開しないとどのような利点がありますか?それはPythonが "先を計画している"ので、あなたはその多くのメモリを再び使うかもしれないと考えているからですか?

  2. 特に、50.5mbがリリースされるのはなぜですか? - それに基づいてリリースされる金額はいくらですか?

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

+3

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

+0

あなたの質問は複数のことを尋ねます - そのうちのいくつかはダブですが、その中には不適切なものがありますが、そのうちのいくつかは良い質問かもしれません。 Pythonがメモリを解放しないかどうか、それができるかどうか、根底にあるメカニズム、それがなぜそのように設計されたのか、回避策があるのか​​、それとも完全に他のものがあるのか​​を尋ねていますか? – abarnert

+2

@abarnert私は類似したサブクラスを組み合わせました。あなたの質問に答えるには:私は、PythonがOSにいくつかのメモリをリリースするが、なぜそれがすべてではないのか、それがなぜあるのかという理由を知っている。それができない状況がある場合、なぜですか?どのような回避策もあります。 – Jared

答えて

70

ヒープに割り当てられたメモリには、ハイウォーターマークが付いている可能性があります。これは、Pythonの内部最適化によって4つのKiBプールに小さなオブジェクト(PyObject_Malloc)を割り当て、8バイトの倍数 - 最大256バイト(3.3の512バイト)の割り当てサイズに分類されます。プール自体は256 KiBアリーナ内にあるため、1つのプール内の1つのブロックを使用すると、256 KiBアリーナ全体が解放されません。 Python 3.3では、小さなオブジェクトアロケータはヒープの代わりに匿名のメモリマップを使用するように切り替えられました。

さらに、ビルトインタイプは、小さなオブジェクトアロケータを使用する場合と使用しない場合がある、以前に割り当てられたオブジェクトのフリーリストを維持します。 intタイプでは、割り当てられたメモリを持つフリーリストが保持され、クリアするにはPyInt_ClearFreeList()を呼び出す必要があります。これは完全にgc.collectを実行することによって間接的に呼び出すことができます。

このように試してみてください。 psutilへのリンクは次のとおりです。

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オブジェクトは作成されません。たとえそれがあったとしても、3.xのintタイプは、基本的にフリーリストを実装していない2.x longです。私はあなたが本当にここで気に質問を推測している

90

は次のとおりです。

は、あなたが知っていればあなたは多くのことを使用されることはありません(使用されたすべてのメモリを解放するためのPythonを強制する方法はあります再びメモリ)?

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

5分間500MBの一時記憶域が必要だが、その後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の種類のゆっくりとしたいくつかのプラットフォームです。ここではミリ秒単位で話しています。分単位ではありません。300秒分の作業を子供1人でこなせば、気付かないことさえあります。しかし、それは無料ではありません。
  • あなたが本当に使い、一時大量のメモリがある場合は、これを行うことはあなたのメインプログラムがスワップアウトを取得することがあります。もちろん、あなたは長期的には時間を節約しています。なぜなら、そのメモリが永遠にぶら下がっていれば、ある時点でスワップする必要があるからです。しかし、これにより、徐々に遅くなり、いくつかのユースケースで非常に顕著なオールアットワンス(および早期)の遅延になる可能性があります。
  • は、プロセス間で大量のデータを送信すると、遅くなることがあります。再び、あなたは、引数の2Kの上に送信し、バック結果の64Kを得ることについて話しているならば、あなたもそれを意識することはありませんが、あなたは大量のデータを送受信している場合、あなたはいくつかの他のメカニズムを使用したいと思います(ファイル、mmap pedまたはそれ以外;共有メモリAPIはmultiprocessingなど)。
  • プロセス間で大量のデータを送信することは、データをpickleable(または、ファイルまたは共有メモリに貼り付ける場合はstruct-able、理想的には​​-able)にする必要があることを意味します。
+12

誰でも落として、理由を説明してくれますか? – abarnert

+0

本当に素敵なトリックですが、問題を解決していません。(しかし、私は本当に好きです。 – ddofborg

24

eryksunは、質問#1に答えた、と私は質問#3(元#4)答えましたが、今の質問#2答えてみましょう:なぜそれは特に50.5メガバイトを解放ん

を - それに基づいてリリースされる金額はいくらですか?

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

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

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

割り当てられたページを実際に測定している場合(実際には非常に便利なことではありませんが、質問しているようです)、ページは実際に割り当てが解除されています。起こった:brkまたは同等のデータセグメントを(最近はまれに)縮小するために使用したか、munmapまたは類似のものを使用してマップされたセグメントを解放したかのいずれかです。 (マップされたセグメントの一部を解放する方法がある(つまり、すぐにマップ解除するMADV_FREEセグメントの場合はMAP_FIXEDで盗むなどの方法もあります)。

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

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

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

、あなたはそれが実装されていますかだけでなく、オブジェクトアロケータの内部状態を知っている必要があります。 (あなたがブロック内の最後の割り当てを解除していない限り、それは明らかに起こることができず、その後も、それが起こらないことがあります。)

をあなたはこれが原因かどうかを知るために、オブジェクトストレージの割り当て解除ブロックを行う場合freeコールでは、PyMemアロケータの内部状態とその実装方法を知る必要があります。 (ここでも、あなたはmalloc編領域内の最後の使用中のブロックの割り当てを解除する必要があり、その後も、起こらないことがあります。)

をあなたはfreemalloc編地域を行う場合は、これが原因かどうかを知るためにmunmapまたは同等のもの(またはbrk)の場合は、mallocの内部状態とその実装方法を知る必要があります。これは、他のプラットフォームとは異なり、プラットフォーム固有のものです。 (また、一般的には最後に使用中のmallocmmapセグメント内に割り当て解除する必要がありますが、それでも発生しない可能性があります)。

なぜ、50.5MBを正確にリリースしたのか、あなたはそれを底から上まで追跡しなければならないでしょう。なぜmallocは1つ以上のfreeコールを実行したときに50.5MBのページをアンマップしたのでしょうか(おそらく50.5MBを少し上回ります)?あなたのプラットフォームのmallocを読んでから、さまざまなテーブルとリストを歩いて現在の状態を確認する必要があります。 (一部のプラットフォームでは、システムレベルの情報を利用することさえありますが、システムのスナップショットをオフラインで調べなければキャプチャすることはほとんど不可能ですが、幸運なことにこれは通常問題ではありません)。それ以上の3つのレベルで同じことをしてください。

この質問に対する唯一の有益な回答は、「理由」です。

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

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

+0

私は複数の回答を投稿することができませんでした.... – laike9m

関連する問題