大きな入力ファイルを操作して、三角形を表す数百万のオブジェクトを作成するPythonプログラムを書きました。アルゴリズムは次のとおりです。
三角形を出力する前に頂点の完全なリストを出力するというOFFの要件は、出力をファイルに書き込む前に、メモリに三角形のリストを保持する必要があることを意味します。その間に、リストのサイズが原因でメモリエラーが発生します。
データを必要としなくなり、解放できるようになったことをPythonに伝えるための最良の方法は何ですか?
Python Official Documentation によれば、gc.collect()
を使ってガベージコレクタに参照されていないメモリを解放させることができます。例:
import gc
gc.collect()
残念なことに(あなたのPythonのバージョンやリリースによって)、ある種のオブジェクトは "ローカルリスト"を使用しますが、これはきちんとしたローカル最適化ですが、特にそのため、「一般資金」には利用できません。
メモリの大量で一時的な使用が完了したときにすべてのリソースをシステムに返すことを保証する唯一の本当に信頼性の高い方法は、その使用をサブプロセスで発生させることです。そのような条件下では、オペレーティングシステムは仕事をして、サブプロセスが浪費しているかもしれないすべてのリソースを喜んでリサイクルします。幸いなことに、multiprocessing
モジュールはこの種の操作(以前はかなり苦痛でした)を最近のバージョンのPythonでそれほど悪くしません。
あなたのユースケースでは、サブプロセスがいくつかの結果を蓄積し、しかもそれらの結果がメインプロセスに利用可能であることを確実にするための最善の方法はセミテンポラリファイルを使うことです。閉じたときに自動的に消えます。普通のファイルは、すべて終わったら明示的に削除してください。
del
ステートメントは役に立つかもしれませんが、IIRCメモリの解放を保証するものではありません。 ドキュメントはこちら ...で、リリースされていない理由 はこちら です。
私はLinuxやUnix系のシステムでPythonのプロセスに仕事をさせようとし、結果を出してそれを殺しているのを聞いたことがあります。
この記事 はPythonガベージコレクタについてのメモを持っていますが、私はメモリ制御の欠如がマネージドメモリのマイナス面であると思います
Pythonはガベージコレクションされているので、リストのサイズを小さくするとメモリが再利用されます。変数を完全に取り除くために "del"ステートメントを使うこともできます。
biglist = [blah,blah,blah]
#...
del biglist
明示的にメモリを解放することはできません。あなたがする必要があるのは、オブジェクトへの参照を保持しないようにすることです。その後、それらはガベージコレクションされ、メモリが解放されます。
あなたの場合、大きなリストが必要なときは、通常は代わりにジェネレータ/イテレータを使用して、コードを再編成する必要があります。そうすれば、大きなリストをメモリに置く必要がまったくなくなります。
http://www.prasannatech.net/2009/07/introduction-python-generators.html
(del
は、他の参照がないときに削除可能であるとしてオブジェクトをマークするので、あなたの友人になることができます。今では、CPythonインタプリタは後で使用するためにこのメモリを保持します。
たぶんあなたはあなたのデータのためのよりコンパクトな構造を使うことによって最初の場所で少しのメモリ問題にも遭遇しないでしょう。したがって、数値のリストは、標準のarray
モジュールまたはサードパーティのnumpy
モジュールで使用されている形式よりもメモリ効率がはるかに低くなります。頂点をNumPy 3 x Nの配列に、三角形をN要素の配列に配置すると、メモリを節約できます。
他の人たちは、あなたがPythonインタプリタを「解放して」メモリを解放することができるかもしれない(あるいはメモリの問題を避けるために)ことができるかもしれないいくつかの方法を投稿しました。たぶんあなたはあなたが最初に彼らの考えを試してみるべきであるということです。しかし、私はあなたにあなたの質問に対する直接的な答えを与えることが重要であると感じます。
Pythonに直接メモリを解放するように指示する方法は実際にはありません。そのことの事実は、あなたがそれほど低いレベルの制御を望むならば、あなたはCまたはC++で拡張を書かなければならないだろうということです。
そうは言っても、これに役立つツールがいくつかあります。
ファイルからグラフを読み込む際にも、同様の問題がありました。処理には、メモリに収まらない20万x 200,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
頂点の再利用を気にしないのであれば、2つの出力ファイルがあります。1つは頂点用、もう1つは三角形用です。その後、三角形ファイルを頂点ファイルに追加します。