Pythonでメモリを明示的に解放するにはどうすればよいですか?


387

大きな入力ファイルを操作して三角形を表す数百万のオブジェクトを作成するPythonプログラムを作成しました。アルゴリズムは次のとおりです。

  1. 入力ファイルを読み込む
  2. ファイルを処理し、頂点で表される三角形のリストを作成する
  3. 頂点をOFF形式で出力します。頂点のリストの後に三角形のリストが続きます。三角形は頂点のリストへのインデックスで表されます

三角形を出力する前に頂点の完全なリストを出力するというOFFの要件は、出力をファイルに書き込む前に三角形のリストをメモリに保持する必要があることを意味します。その間、リストのサイズが原因でメモリエラーが発生します。

一部のデータが不要になり、解放できることをPythonに伝える最善の方法は何ですか?


11
三角形を中間ファイルに出力して、必要なときにもう一度読み取ってみませんか?
アリスパーセル

2
この質問は、2つのまったく異なるものになる可能性があります。それらのエラーは、同じPythonプロセスからのものであり、その場合、メモリをPythonプロセスのヒープに解放することを考慮しますか、それとも、システム上の異なるプロセスからのエラーであり、その場合、OSにメモリを解放することを考慮しますか?
Charles Duffy、

回答:


455

Pythonの公式ドキュメントによると、ガベージコレクターに参照されていないメモリを強制的に解放させることができますgc.collect()。例:

import gc
gc.collect()

19
いずれにせよ、とにかく頻繁にガベージコレクションが行われるため、例外的なケースを除いて、あまり役に立たないと思います。
Lennart Regebro、2009

24
一般に、gc.collect()は避けてください。ガベージコレクタは、その処理方法を知っています。そうは言っても、OPが大量のオブジェクトの割り当てを突然解除している場合(数百万など)、gc.collectが役立つことがあります。
Jason Baker、

165
実際にgc.collect()ループの最後に自分自身を呼び出すと、メモリの断片化を回避でき、パフォーマンスの維持に役立ちます。私はこれが大きな違いを生むのを見てきました(〜20%ランタイムIIRC)
RobM

39
私はpython 3.6を使用しています。gc.collect()hdf5(500k行)からpandasデータフレームを読み込んだ後に呼び出すと、メモリ使用量が1.7GBから500MBに減少しました
John

15
32GBのメモリを搭載したシステムで、25GBの数の多い配列をロードして処理する必要があります。使用del my_arrayに続いてgc.collect()配列を処理するメモリが実際に解放され、私のプロセスは次の配列をロードするために生き残る唯一の方法です後。
デビッド

113

残念ながら(Pythonのバージョンとリリースに応じて)、一部のタイプのオブジェクトは「フリーリスト」を使用しますが、これはきちんとしたローカル最適化ですが、特定のタイプのオブジェクトのみに「メモリ」を「マーク」することにより、メモリの断片化を引き起こす可能性があります。それにより「一般基金」は利用できません。

メモリの大規模で一時的な使用がすべてのリソースをシステムに返すことを確実にする唯一の本当に信頼できる方法は、その使用をサブプロセスで実行し、メモリを大量に消費する作業を終了させることです。このような状況下では、オペレーティングシステムがその役割を果たし、サブプロセスが消費したすべてのリソースを喜んでリサイクルします。幸いなことに、このmultiprocessingモジュールは、この種の操作(以前はかなり面倒でした)を最新バージョンのPythonではそれほど悪くしません。

あなたのユースケースでは、サブプロセスがいくつかの結果を蓄積し、それらの結果がメインプロセスで利用可能であることを保証する最善の方法は、セミテンポラリファイルを使用することです(つまり、セミテンポラリファイルではなく、閉じたときに自動的に消えます。通常のファイルは、すべて完了したら明示的に削除します)。


31
私はこの簡単な例を見たいと思います。
アーロンホール

3
真剣に。@AaronHallが言ったこと。
Noobサイボット2014年

17
@AaronHallの簡単な例が利用可能multiprocessing.Managerなりました。ファイルではなく共有状態を実装するために使用します。
user4815162342 2014年

48

del声明は、使用のかもしれませんが、IIRC 、メモリを解放することは保証されませんドキュメントはここにある ...そして、それが解除されていない理由はここにあります

LinuxおよびUnixタイプのシステムを使用している人が、Pythonプロセスに作業を依頼して結果を出し、それを強制終了するのを聞いたことがあります。

この記事にはPythonガベージコレクターに関するメモがありますが、メモリ制御の欠如はマネージメモリの欠点です


IronPythonとJythonは、この問題を回避するための別のオプションでしょうか?
エステバンキューバー2009

@voyager:いいえ、そうではありません。そして、実際には他の言語もそうではありませんでした。問題は、彼が大量のデータをリストに読み込んでいることと、データがメモリに対して大きすぎることです。
Lennart Regebro、2009

1
IronPythonまたはJythonの場合は、さらに悪化する可能性があります。これらの環境では、参照を保持しているメモリが他にない場合にメモリが解放される保証さえありません。
Jason Baker、

@voyager、そうです、Java仮想マシンは解放するメモリをグローバルに探します。JVMにとって、Jythonは特別なものではありません。一方、JVMには独自の欠点があります。たとえば、JVMが使用できるヒープの大きさを事前に宣言する必要があります。
ファルケン教授の契約が2013年

32

Pythonはガベージコレクションされるため、リストのサイズを小さくすると、メモリが解放されます。「del」ステートメントを使用して、変数を完全に取り除くこともできます。

biglist = [blah,blah,blah]
#...
del biglist

18
これは事実であり、そうではありません。リストのサイズを小さくすると、メモリを回収できますが、いつ発生するかは保証されません。
user142350 2009

3
いいえ、ただし通常は役立ちます。ただし、ここでの質問を理解しているように、問題は、オブジェクトを多く持つ必要があるため、リストに読み込む場合、すべてを処理する前にメモリが不足することです。彼が処理を完了する前にリストを削除することは、有用な解決策になることはほとんどありません。;)
Lennart Regebro 2009

3
メモリ不足/メモリ不足の状態は、ガベージコレクタの「緊急実行」を引き起こしませんか?
ジェレミー・フリースナー、2009

4
biglist = []はメモリを解放しますか?
ネオウイグル2016

3
はい、古いリストが他から参照されていない場合。
Ned Batchelder 2016

22

明示的にメモリを解放することはできません。オブジェクトへの参照を保持しないようにする必要があります。その後、ガベージコレクションが行われ、メモリが解放されます。

大規模なリストが必要な場合、通常は代わりにジェネレーター/イテレーターを使用して、コードを再編成する必要があります。そうすれば、メモリに大きなリストを置く必要がまったくなくなります。

http://www.prasannatech.net/2009/07/introduction-python-generators.html


1
このアプローチが実行可能な場合は、おそらく実行する価値があります。ただし、イテレータでランダムアクセスを行うことはできないため、問題が発生する可能性があることに注意してください。
Jason Baker、

それは事実であり、それが必要な場合、大きなデータデータセットにランダムにアクセスするには、何らかのデータベースが必要になる可能性があります。
Lennart Regebro、2009

イテレータを簡単に使用して、別のイテレータのランダムなサブセットを抽出できます。
S.Lott、2009

そうですが、サブセットを取得するためにすべてを反復処理する必要がありますが、これは非常に遅くなります。
Lennart Regebro、2009

21

del他の参照がない場合にオブジェクトを削除可能としてマークするので、あなたの友達になることができます。現在、多くの場合、CPythonインタープリターが後で使用するためにこのメモリを保持しているため、オペレーティングシステムが「解放された」メモリを認識しない場合があります。)

データにもっとコンパクトな構造を使用することで、最初からメモリの問題に直面することはないでしょう。したがって、数値のリストは、標準arrayモジュールまたはサードパーティnumpyモジュールで使用される形式よりもメモリ効率がはるかに低くなります。頂点をNumPy 3xN配列に配置し、三角形をN要素配列に配置することで、メモリを節約できます。


え?CPythonのガベージコレクションは、refcountingベースです。(多くの一般的なJVM実装の場合のように)定期的なマークアンドスイープではなく、参照カウントがゼロになるとすぐに何かをすぐに削除します。定期的なメンテナンスが必要なのは、循環(参照カウントがゼロになるが、参照ツリーのループが原因ではない)だけです。delオブジェクトを参照するすべての名前に別の値を再割り当てするだけではできないことは何もしません。
Charles Duffy、

あなたがどこから来たのかわかります。それに応じて回答を更新します。CPythonインタープリターは実際には中間的な方法で機能することを理解しています。Python delの観点からはメモリを解放しますが、一般的にはCランタイムライブラリやOSの観点からは解放しません。参考文献:stackoverflow.com/a/32167625/4297effbot.org/pyfaq/...
Eric O Lebigot

リンクの内容については同意しましたが、OPが同じPythonプロセスから発生するエラーについてOPが話していると仮定すると、メモリをプロセスローカルヒープとOSに解放することの区別は関係がないようです(ヒープに解放すると、そのPythonプロセス内の新しい割り当てにそのスペースが利用可能になります)。そのため、delexits-from-scope、reassignmentsなどでも同様に有効です
Charles Duffy

11

ファイルからグラフを読み取る際にも同様の問題がありました。処理には、メモリに収まらない200 000x200 000のフロートマトリックス(一度に1行)の計算が含まれていました。gc.collect()問題のメモリ関連の側面を修正して計算間でメモリを解放しようとすると、パフォーマンスの問題が発生しました。理由はわかりませんが、使用されるメモリの量は一定のままでしたが、新しい呼び出しを行うgc.collect()たびに、前のもの。そのため、ガベージコレクションは、計算時間のほとんどを非常に速く費やしました。

メモリとパフォーマンスの両方の問題を修正するために、どこかで一度読んだマルチスレッドトリックの使用に切り替えました(申し訳ありませんが、関連する投稿はもう見つかりません)。大きなforループでファイルの各行を読み取り、処理し、時々実行しgc.collect()てメモリ領域を解放する前に。次に、新しいスレッドでファイルのチャンクを読み取って処理する関数を呼び出します。スレッドが終了すると、奇妙なパフォーマンスの問題なしにメモリが自動的に解放されます。

実際には次のように機能します。

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
コメントにPythonでは#ではなく `//` `を使用しているのはなぜでしょうか。
JCロカモンデ

言語間で混乱しました。発言ありがとうございます、構文を更新しました。
Retzod 2018

9

他の人たちは、Pythonインタープリターを "coax"してメモリを解放できる(またはメモリの問題を回避できる)いくつかの方法を投稿しています。おそらく、最初に彼らのアイデアを試してみる必要があります。しかし、あなたの質問に直接答えることが重要だと思います。

Pythonにメモリを解放するように直接指示する方法は実際にはありません。実際のところ、その低いレベルの制御が必要な場合は、CまたはC ++で拡張機能を作成する必要があります。

とはいえ、これを支援するいくつかのツールがあります:


3
大量のメモリを使用している場合、gc.collect()およびdel gc.garbage [:]は問題なく動作します
Andrew Scott Evans

3

頂点の再利用を気にしない場合は、2つの出力ファイルを作成できます。1つは頂点用、もう1つは三角形用です。次に、三角形ファイルを頂点ファイルに追加します。


1
頂点のみをメモリに保持して三角形をファイルに出力し、最後に頂点のみを出力できると考えています。ただし、三角形をファイルに書き込むという行為は、パフォーマンスを大幅に低下させます。速度にどのような方法がありそれまでは?
ネイサン・フェルマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.