Pythonでforループを並列化する


35

MatlabのparforのようなPythonのツールはありますか?このスレッドを見つけましたが、4年前です。ここの誰かが最近の経験を持っているかもしれないと思った。

並列化するタイプの例を次に示します。

X = np.random.normal(size=(10, 3))
F = np.zeros((10, ))
for i in range(10):
    F[i] = my_function(X[i,:])

どこmy_function取りndarrayのサイズの(1,3)スカラを返します。

少なくとも、複数のコアを同時に使用したい--- parforのように。つまり、8〜16コアの共有メモリシステムを想定しています。


グーグルでたくさんの結果。これらは非常に単純なようです:blog.dominodatalab.com/simple-parallelization quora.com/What-is-the-Python-equivalent-of-MATLABs-parfor
ダグリピンスキー

ありがとう、@ doug-lipinski。これらの例は、グーグルで見つけた他の例と同様に、反復インデックスに基づいた簡単な計算をしています。そして、彼らはコードが「信じられないほど簡単だ」といつも主張します。私の例では、forループの外側で配列を定義します(メモリを割り当てます)。私は別の方法でそれをやって大丈夫です。それはちょうど私がMatlabでそれを行う方法です。これらの例に逆らうように見えるトリッキーな部分は、ループ内の関数に与えられた配列の一部を取得することです。
ポールG.コンスタンティン

回答:


19

Joblibはあなたが望むことをします。基本的な使用パターンは次のとおりです。

from joblib import Parallel, delayed

def myfun(arg):
     do_stuff
     return result

results = Parallel(n_jobs=-1, verbose=verbosity_level, backend="threading")(
             map(delayed(myfun), arg_instances))

where arg_instancesmyfun、並行して計算される値のリストです。主な制限はmyfun、トップレベル関数でなければならないということです。backendパラメータは、いずれかになります"threading""multiprocessing"

追加の共通パラメーターを並列化関数に渡すことができます。の本体はmyfun、初期化されたグローバル変数、つまり子が利用できる値も参照できます。

スレッドバックエンドでは引数と結果はほとんど何でもかまいませんが、結果はマルチプロセッシングバックエンドでシリアル化できる必要があります。


Daskも同様の機能を提供します。コア外のデータで作業している場合、またはより複雑な計算を並列化しようとしている場合に適しています。


マルチプロセッシングを含むバッテリーを使用するためにゼロ値が追加されています。joblibが内部でそれを使用していることは間違いないでしょう。
ザビエルコンベル

1
それはJOBLIBは、魔法ではないことを言及する必要があるthreadingから、バックエンド被るGILのボトルネックmultiprocessingバックエンドが原因のすべてのパラメータと戻り値の直列化に大きなオーバーヘッドをもたらします。Pythonでの並列処理の低レベルの詳細については、この回答を参照してください。
ヤクブクリンコフスキー

joblibがforループより高速になる関数の複雑さと反復回数の組み合わせを見つけることができません。私にとっては、n_jobs = 1の場合は同じ速度であり、他のすべての場合ははるかに遅くなります
Aleksejs Fomins

@AleksejsFominsスレッドベースの並列処理は、GILをリリースしないが、かなりの数のコード、特にデータサイエンスや数値ライブラリには役立ちません。それ以外の場合、マルチプロセッシングが必要な場合、Jobliは両方をサポートします。また、マルチプロセッシングモジュールにmapは、直接使用できる並列機能もあります。また、mklコンパイル済みnumpyを使用すると、何もせずにベクトル化された操作が自動的に並列化されます。Anancondaのnumpyは、デフォルトでmklが有効になっています。しかし、普遍的な解決策はありません。JOBLIBは非常に低い大騒ぎで、より少ないotionsは2015年にあった
ダニエル・マーラー

アドバイスをしてくれてありがとう。以前はマルチプロセッシングを試みたことがあり、数件の投稿を書いたのを覚えています。期待どおりにスケーリングしなかったからです。別の見方をする必要があるかもしれません
Aleksejs Fomins

9

探しているのはNumbaです。これはforループを自動並列化できます。彼らのドキュメントから

from numba import jit, prange

@jit
def parallel_sum(A):
    sum = 0.0
    for i in prange(A.shape[0]):
        sum += A[i]

    return sum

8

このような単純なループを並列化するためには、my_function選択時に特別なことを想定しないことをmultiprocessing.Pool().map()お勧めします。joblibdaskmpi計算やnumbaルックスは、(彼らは行き過ぎている要約すると)、このようなユースケースのための任意の利点をもたらすと無用の依存関係を追加していない他の回答で提案されているような。別の回答で提案されているようにスレッドを使用することは、コードのGIL相互作用に親しむ必要があるか、コードが主に入出力を行う必要があるため、良い解決策にはなりません。

それはnumba、連続した純粋なpythonコードを高速化するための良いアイデアかもしれませんが、これは質問の範囲外であると感じています。

import multiprocessing
import numpy as np

if __name__ == "__main__":
   #the previous line is necessary under windows to not execute 
   # main module on each child under windows

   X = np.random.normal(size=(10, 3))
   F = np.zeros((10, ))

   pool = multiprocessing.Pool(processes=16)
   # if number of processes is not specified, it uses the number of core
   F[:] = pool.map(my_function, (X[i,:] for i in range(10)) )

ただし、いくつかの注意事項があります(ただし、ほとんどのアプリケーションには影響しません)。

  • Windowsでは、フォークはサポートされていないため、メインモジュールを備えたインタープリターが各子の起動時に起動されるため、オーバーヘッドが発生する可能性があります(広告の理由です) if __name__ == "__main__"
  • 引数とはmy_functionの結果は漬物やアンピックルされ、それを低減するため、この答えを参照してください、あまりにも大きなオーバーヘッドかもしれませんhttps://stackoverflow.com/a/37072511/128629。また、選択不可能なオブジェクトを使用不可にします
  • my_function状態はプロセス間で共有されないため、グローバル変数との通信などの共有状態に依存しないでください。純粋な関数(数学的な意味での関数)は、状態を共有しない関数の例です

6

parforの私の印象は、MATLABが実装の詳細をカプセル化しているため、共有メモリ並列処理(必要なもの)と分散メモリ並列処理(MATLAB分散コンピューティングサーバーを実行している場合)の両方を使用している可能性があることです。

共有メモリの並列処理が必要で、何らかのタスク並列ループを実行している場合、マルチプロセッシング標準ライブラリパッケージはおそらく、Dougの投稿で言及されているように、joblibのような素晴らしいフロントエンドを備えたものです。標準ライブラリはなくなることはなく、維持されているため、リスクは低くなります。

Parallel PythonIPythonの並列機能など、他にもオプションがあります。Parallel Pythonを少し見ると、ライブラリが分散ケースの詳細をカプセル化しているという点で、parforの精神に近いと思いますが、そうするためのコストは、エコシステムを採用しなければならないことです。IPythonを使用するコストは同様です。IPythonの方法を採用する必要がありますが、これは価値がある場合もあれば、そうでない場合もあります。

分散メモリに関心がある場合は、mpi4pyをお勧めします。Lisandro Dalcinは素晴らしい仕事をしており、mpi4pyはPETSc Pythonラッパーで使用されているので、すぐになくなるとは思いません。マルチプロセッシングと同様に、これはparforよりも並列化に対する低レベルのインターフェイスですが、しばらくは続く可能性があります。


ありがとう、@ Geoff。これらのライブラリを使用した経験はありますか?多分、共有メモリマシン/マルチコアプロセッサでmpi4pyを使用してみます。
ポールG.コンスタンティン

@PaulGConstantine mpi4pyを正常に使用しました。MPIに精通していれば、非常に簡単です。私はマルチプロセッシングを使用していませんが、同僚に勧めました。私もIPythonを使用しましたが、並列処理機能は使用していませんので、どのように機能するかについて話すことはできません。
ジェフオックスベリー

1
:アロンは素晴らしいを持って、彼はスーパーでPyHPCコースの準備チュートリアルmpi4py github.com/pyHPC/pyhpc-tutorial
マットKnepley

4

「一般的な」Python関数を並列実行するために使用できる「ブラックボックス」ツールを探す前にmy_function()、手動で並列化する方法を分析することをお勧めします。

まず、実行時間my_function(v)とpython forループのオーバーヘッドを比較します。[C] Python forループはかなり遅いため、費やされる時間はmy_function()ごくわずかです。

>>> timeit.timeit('pass', number=1000000)
0.01692986488342285
>>> timeit.timeit('for i in range(10): pass', number=1000000)
0.47521495819091797
>>> timeit.timeit('for i in xrange(10): pass', number=1000000)
0.42337894439697266

my_function(v)ループを必要としない単純なベクトル実装があるかどうかを2番目に確認します。F[:] = my_vector_function(X)

(これら2つの最初のポイントは非常に些細なことです。完全を期すためにここで言及した場合はご容赦ください。)

少なくともCPython実装の場合の3番目の最も重要なポイントは、my_functionそのほとんどの時間がグローバルインタープリターロック、またはGILの内部である外部であるかを確認することです。GIL以外で時間を費やす場合は、標準ライブラリモジュールを使用する必要があります。(ここに例があります)。ところで、GILをリリースするためだけにCの拡張として書くことを考えることができます。threadingmy_function()

最後に、my_function()GILをリリースしない場合、multiprocessingモジュールを使用できます

参照:同時実行に関するPythonドキュメント、および並列処理に関するnumpy / scipyイントロ


2

ジュリアを試すことができます。Pythonにかなり近く、多くのMATLABコンストラクトがあります。ここでの翻訳は:

F = @parallel (vcat) for i in 1:10
    my_function(randn(3))
end

これにより、乱数も並行して作成され、結果は縮小中に最終的に連結されます。これはマルチプロセッシングを使用します(したがってaddprocs(N)、使用する前にプロセスを追加する必要があります。これは、このブログ投稿に示すように、HPC上の複数のノードでも機能します)。

pmap代わりに使用することもできます:

F = pmap((i)->my_function(randn(3)),1:10)

スレッドの並列処理が必要な場合は、使用できますThreads.@threads(ただし、アルゴリズムをスレッドセーフにする必要があります)。Juliaを開く前に、環境変数JULIA_NUM_THREADSを設定すると、次のようになります。

Ftmp = [Float64[] for i in Threads.nthreads()]
Threads.@threads for i in 1:10
    push!(Ftmp[Threads.threadid()],my_function(randn(3)))
end
F = vcat(Ftmp...)

ここでは、スレッドごとに個別の配列を作成します。そのため、配列に追加するときにそれらが衝突せず、その後配列を連結するだけです。スレッド化は非常に新しいため、現在はスレッドをそのまま使用していますが、スレッド処理とマップはマルチプロセッシングの場合と同じように追加されると確信しています。


0

joblibライブラリの並列機能と遅延機能を使用することをお勧めします。「tempfile」モジュールを使用して、巨大な配列の一時共有メモリを作成します。例と使用方法は、https: //pythonhosted.org/joblib/parallel.htmlにあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.