multiprocessing.Pool()
を使用して、いくつかの重い計算を並列化します。
ターゲット関数は大量のデータ(巨大なリスト)を返します。 RAMが不足しています。
multiprocessing
がなければ、結果の要素が計算されるときに次々にyield
することで、ターゲット関数をジェネレーターに変更します。
マルチプロセッシングはジェネレーターをサポートしていないことを理解しています-出力全体を待って一度に返しますよね?降伏なし。結果配列全体をRAMに構築せずに、Pool
ワーカーがデータを利用可能になるとすぐに生成するようにする方法はありますか?
簡単な例:
def target_fnc(arg):
result = []
for i in xrange(1000000):
result.append('dvsdbdfbngd') # <== would like to just use yield!
return result
def process_args(some_args):
pool = Pool(16)
for result in pool.imap_unordered(target_fnc, some_args):
for element in result:
yield element
これはPython 2.7です。
これは、キューの理想的なユースケースのように聞こえます: http://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes
プールされたワーカーから結果をキューにフィードし、マスターに取り込むだけです。
ワーカーがキューに入力するのとほぼ同じ速度でキューをドレインしない限り、メモリ不足の問題が発生する可能性があることに注意してください。キューサイズ(キューに収まるオブジェクトの最大数)を制限できます。その場合、プールされたワーカーは、キューでスペースが使用可能になるまでqueue.put
ステートメントでブロックします。これにより、メモリ使用量に上限が設けられます。 しかしこれを行っている場合は、プーリングが必要かどうか、および/またはより少ないワーカーを使用することが理にかなっているのかどうかを再検討する時期かもしれません。
あなたの説明から、100万要素のlist
を返すことを避けるように、データが入ってくるほどデータを処理することにあまり興味がないように思えます。
これを行う簡単な方法があります。データをファイルに入れるだけです。例えば:
def target_fnc(arg):
fd, path = tempfile.mkstemp(text=True)
with os.fdopen(fd) as f:
for i in xrange(1000000):
f.write('dvsdbdfbngd\n')
return path
def process_args(some_args):
pool = Pool(16)
for result in pool.imap_unordered(target_fnc, some_args):
with open(result) as f:
for element in f:
yield element
明らかに、結果に改行が含まれる可能性がある場合、または文字列などでない場合は、単純なテキストファイルの代わりにcsv
ファイル、numpy
などを使用することをお勧めしますが、考え方は同じです。
そうは言っても、これが単純であっても、データを一度に1チャンクで処理することには通常利点があるため、タスクを分割するか、(他の2つの回答が示唆するように)Queue
を使用する方がよい場合があります。欠点(それぞれ、タスクを分割する方法が必要な場合、またはデータが生成されるのと同じ速さでデータを消費できる必要がある場合)が取引を妨げるものではない場合。
タスクがデータをチャンクで返すことができる場合…タスクをより小さなタスクに分割して、それぞれが1つのチャンクを返すことができますか?明らかに、これが常に可能であるとは限りません。そうでない場合は、他のメカニズムを使用する必要があります(Loren Abramsが示唆しているように、Queue
など)。しかし、それがisの場合、この問題を解決するだけでなく、他の理由からもおそらくより良い解決策です。
あなたの例では、これは確かに実行可能です。例えば:
def target_fnc(arg, low, high):
result = []
for i in xrange(low, high):
result.append('dvsdbdfbngd') # <== would like to just use yield!
return result
def process_args(some_args):
pool = Pool(16)
pool_args = []
for low in in range(0, 1000000, 10000):
pool_args.extend(args + [low, low+10000] for args in some_args)
for result in pool.imap_unordered(target_fnc, pool_args):
for element in result:
yield element
(もちろん、ループをネストされた内包表記、または必要に応じてZip
とflatten
に置き換えることもできます。)
したがって、some_args
は[1, 2, 3]
、300のタスクを取得します—[[1, 0, 10000], [2, 0, 10000], [3, 0, 10000], [1, 10000, 20000], …]
、それぞれが1000000ではなく10000要素のみを返します。