Python(Scipy、Numpy)に対するMATLABのparforに対する明確な答えを探しています。
Parforに似たソリューションはありますか?そうでない場合、それを作成するための複雑さは何ですか?
更新:これは私がスピードアップする必要がある典型的な数値計算コードです
import numpy as np
N = 2000
output = np.zeros([N,N])
for i in range(N):
for j in range(N):
output[i,j] = HeavyComputationThatIsThreadSafe(i,j)
重い計算関数の例は次のとおりです。
import scipy.optimize
def HeavyComputationThatIsThreadSafe(i,j):
n = i * j
return scipy.optimize.anneal(lambda x: np.sum((x-np.arange(n)**2)), np.random.random((n,1)))[0][0,0]
多くの 並列計算のためのPythonフレームワーク があります。私がたまたま好きなのは IPython ですが、他のことについてはあまり知りません。 IPythonでは、parforに類似する1つはclient.MultiEngineClient.map()
または 迅速かつ簡単な並列処理に関するドキュメント の他の構成要素の一部です。
pythonに組み込まれているものはmultiprocessing
になりますdocs is here 。私は常にmultiprocessing.Pool
をプロセッサと同じ数のワーカーで使用します。次に、forループのような構造を実行する必要があるときはいつでも、Pool.imap
を使用します
関数の本体が以前の反復に依存していない限り、ほぼ線形のスピードアップになります。これには、入力と出力がpickle
- ableであることも必要ですが、これは標準型の場合、非常に簡単に確認できます。
更新:それがいかに簡単かを示すために、更新された関数のいくつかのコード:
from multiprocessing import Pool
from itertools import product
output = np.zeros((N,N))
pool = Pool() #defaults to number of available CPU's
chunksize = 20 #this may take some guessing ... take a look at the docs to decide
for ind, res in enumerate(pool.imap(Fun, product(xrange(N), xrange(N))), chunksize):
output.flat[ind] = res
例を見るには、PythonでこのMatlabコードと同等のものを記述したいとします。
matlabpool open 4
parfor n=0:9
for i=1:10000
for j=1:10000
s=j*i
end
end
n
end
disp('done')
python特にjupyter Notebookでこれを書き込む方法。作業ディレクトリに関数を作成する必要があります(私はFunForParFor.pyと呼びます)。
def func(n):
for i in range(10000):
for j in range(10000):
s=j*i
print(n)
次に、Jupyterノートブックに移動して、次のコードを記述します
import multiprocessing
import FunForParFor
if __== '__main__':
pool = multiprocessing.Pool(processes=4)
pool.map(FunForParFor.func, range(10))
pool.close()
pool.join()
print('done')
これでうまくいきました!特定の例を示すために、ここでそれを共有したかっただけです。
私は常に Parallel Python を使用してきましたが、特定のオペレーティングシステムでは高価になる可能性のある個別のプロセスを使用するのが普通なので、完全な類似体ではありません。それでも、ループの本体が十分に分厚い場合、これは問題にならず、実際にはいくつかの利点があります。
これは、エレガントに Ray を使用して行うことができます。これは、Pythonコードを簡単に並列化して配布できるシステムです。
サンプルを並列化するには、@ray.remote
デコレータを使用して関数を定義してから、.remote
を使用して関数を呼び出す必要があります。
import numpy as np
import time
import ray
ray.init()
# Define the function. Each remote function will be executed
# in a separate process.
@ray.remote
def HeavyComputationThatIsThreadSafe(i, j):
n = i*j
time.sleep(0.5) # Simulate some heavy computation.
return n
N = 10
output_ids = []
for i in range(N):
for j in range(N):
# Remote functions return a future, i.e, an identifier to the
# result, rather than the result itself. This allows invoking
# the next remote function before the previous finished, which
# leads to the remote functions being executed in parallel.
output_ids.append(HeavyComputationThatIsThreadSafe.remote(i,j))
# Get results when ready.
output_list = ray.get(output_ids)
# Move results into an NxN numpy array.
outputs = np.array(output_list).reshape(N, N)
# This program should take approximately N*N*0.5s/p, where
# p is the number of cores on your machine, N*N
# is the number of times we invoke the remote function,
# and 0.5s is the time it takes to execute one instance
# of the remote function. For example, for two cores this
# program will take approximately 25sec.
multiprocessing モジュールよりもRayを使用することには多くの利点があります。特に、同じコードは、単一のマシンとマシンのクラスターで実行されます。 Rayのその他の利点については、 この関連記事 を参照してください。
注:覚えておくべきポイントの1つは、各リモート関数が別のプロセスで実行されている可能性があることです。リモート関数を呼び出す以上のことが必要です。経験則として、リモート関数の計算は、リモート関数のスケジューリングと起動のオーバーヘッドを償却するのに少なくとも数十ミリ秒かかるはずです。
ここですべての解決策を試しましたが、最も単純な方法であり、MATLABのparforに最も近い方法は numba's prangeであることがわかりました。
基本的に、ループ内の1文字を変更し、範囲をprangeに変更します。
from numba import autojit, prange
@autojit
def parallel_sum(A):
sum = 0.0
for i in prange(A.shape[0]):
sum += A[i]
return sum
Joblib Parallelを試すことをお勧めします。
from joblib import Parallel, delayed
out = Parallel(n_jobs=2)(delayed(heavymethod)(i) for i in range(10))
forループを取る代わりに
from time import sleep
for _ in range(10):
sleep(.2)
操作をリスト内包に書き直す
[sleep(.2) for _ in range(10)]
ここで、式を直接評価するのではなく、実行する必要があることを収集します。これがdelayed
メソッドの目的です。
from joblib import delayed
[delayed(sleep(.2)) for _ in range(10)]
次に、n_workersを使用して並列プロセスをインスタンス化し、リストを処理します。
from joblib import Parallel
r = Parallel(n_jobs=2, verbose=10)(delayed(sleep)(.2) for _ in range(10))
[Parallel(n_jobs=2)]: Done 1 tasks | elapsed: 0.6s
[Parallel(n_jobs=2)]: Done 4 tasks | elapsed: 0.8s
[Parallel(n_jobs=2)]: Done 10 out of 10 | elapsed: 1.4s finished