155

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

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

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

    私のマシンで使用される実際のメモリは80.9mb. そして私は・・・それから私は、

    del foo
    

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

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

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

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

4

4 に答える 4

146

ここであなたが本当に気にかけている質問は次のとおりだと思います。

使用されたすべてのメモリを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または理想的には可能ですctypes)。
于 2013-03-19T06:00:32.883 に答える
95

ヒープに割り当てられたメモリは、ハイ ウォーター マークの影響を受ける可能性があります。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.memory_info().rss

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

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

# collect() calls PyInt_ClearFreeList()
# or use ctypes: pythonapi.PyInt_ClearFreeList()
gc.collect()
mem3 = proc.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 など) は、上部の連続した空き領域が定数、動的、または構成可能なしきい値に達すると、ヒープを縮小します。malloptglibc では、 (M_TRIM_THRESHOLD)でこれを調整できます。これを考えると、ヒープがブロックよりも多く (さらにはもっと) 縮小しても驚くことではありませんfree

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

于 2013-03-17T05:46:25.050 に答える
34

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

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

それが基づいているのは、最終的には、Python 内の一連の偶然の一致であり、malloc予測するのは非常に困難です。

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

または、使用中のページを測定している可能性がありますMADV_FREE.

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

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

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

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

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

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

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

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

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

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

また、リソースが限られている開発を行っている場合、これらの詳細を知っていても役に立ちません。mmapこれらのすべてのレベル、特にアプリケーション レベルで必要なメモリについて最終的な処理を行う必要があります (おそらく、間に単純でよく理解されたアプリケーション固有のゾーン アロケータが 1 つあります)。

于 2013-03-19T19:04:01.753 に答える