PythonにおけるShared Memory
前提
pythonはGILの影響でmulti thread programmingでcpu-bound jobが早くならない.
なので,multiprocessingを使うしかない.
CPythonのmultiprocessingはforkなので,unixならcopy-on-write.なので,globで定義したデータなら,Read-onlyに限り,特段気にしないで共有メモリでタスクがパラレルに使えるはずというのは勘違いのよう.この場合は,ref-countが変わって結局コピーが起こるそう.
結局のところ(multiprocessing documentにあるように), shared memory (Value, Array, RawArray)を使って,データを受け渡すしかないよう.Queue/Pipe経由ではなく,os.forkの前に受け渡す.Arrayはdefaultでlockがあるので,lockを気にしないならRawArrayを使う.
Read-onlyなタスク
タスクは,pairwise-similarity (pairwise-innerproduct)を使い,愚直にnumpyで2 loopsを回す.(10000, 100)の行列で,2回10000のloopでdot productをとる.
ここは,numbaを使う,こんなことやらずに,numpyで計算方法の工夫をするなどが考えられるが,それは,out-of-scope.numbaは,python: 行列演算の高速化を参考にされたい.
行いたい比較検証は下記のとおり
1. 1processでnumpyの計算を行う
2. sharedctypes.RawArray + np.ctypeslibで,2と同じことを行う
3. sharedmemを使う
これらで計算速度を比較する
それぞれの検証結果のコードはここ
各実験を10回実行した時の平均,パラでやっている場合は,最大値をとってからの平均.
結果
Exp. index | Elapsed time (sec) |
---|---|
1 | 123.8867863655 |
2 | 32.0695706606 |
3 | 129.2636404037 |
Writeありなタスク
タスクは,Read-onlyなタスクと同じで,pairwise-similarity (pairwise-innerproduct)だが,結果を共有メモリに書き出す.
行いたい比較検証は下記の通り
1. sharedctypes.Array + np.ctypeslibで書き込まれるobjectを作る
2. sharedctypes.RawArray + np.ctypeslibで書き込まれるobjectを作る
3. sharedmemで書き込まれるobjectを作る
元のデータは,Read-onlyなタスクでRawArray > sharememだったので,RawArrayで共有する.
これらで計算速度を比較する
それぞれの検証結果のコードはここ
各実験を10回実行した時の平均,パラでやっている場合は,最大値をとってからの平均.
結果
Exp. index | Elapsed time (sec) |
---|---|
1 | 37.442804575 |
2 | 37.6960495472 |
3 | 38.0742048979 |
まとめ
ReadもWriteも予想どおりスピードは,#concurrency倍になったが,sharedmemをreadソースとして使った時になぜか,baseの実験(read1)と同じ実行時間になってしまった..., 中身が,mmap.mmapなのが原因なのか...?
それにしては,baseの実験(read1)と同じくらいの実行時間になってしまったことが気がかり...
参考
- http://stackoverflow.com/questions/17785275/share-large-read-only-numpy-array-between-multiprocessing-processes
- http://briansimulator.org/sharing-numpy-arrays-between-processes/
- http://stackoverflow.com/questions/5549190/is-shared-readonly-data-copied-to-different-processes-for-python-multiprocessing/5550156#5550156
- http://stackoverflow.com/questions/14941729/will-os-fork-use-copy-on-write-or-do-a-full-copy-of-the-parent-process-in-pyth