web-dev-qa-db-ja.com

ビッグデータによるマルチプロセッシング

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です。

24
user124114

これは、キューの理想的なユースケースのように聞こえます: http://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes

プールされたワーカーから結果をキューにフィードし、マスターに取り込むだけです。

ワーカーがキューに入力するのとほぼ同じ速度でキューをドレインしない限り、メモリ不足の問題が発生する可能性があることに注意してください。キューサイズ(キューに収まるオブジェクトの最大数)を制限できます。その場合、プールされたワーカーは、キューでスペースが使用可能になるまでqueue.putステートメントでブロックします。これにより、メモリ使用量に上限が設けられます。 しかしこれを行っている場合は、プーリングが必要かどうか、および/またはより少ないワーカーを使用することが理にかなっているのかどうかを再検討する時期かもしれません。

17
Loren Abrams

あなたの説明から、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を使用する方がよい場合があります。欠点(それぞれ、タスクを分割する方法が必要な場合、またはデータが生成されるのと同じ速さでデータを消費できる必要がある場合)が取引を妨げるものではない場合。

4
abarnert

タスクがデータをチャンクで返すことができる場合…タスクをより小さなタスクに分割して、それぞれが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

(もちろん、ループをネストされた内包表記、または必要に応じてZipflattenに置き換えることもできます。)

したがって、some_args[1, 2, 3]、300のタスクを取得します—[[1, 0, 10000], [2, 0, 10000], [3, 0, 10000], [1, 10000, 20000], …]、それぞれが1000000ではなく10000要素のみを返します。

3
abarnert