Pythonで共有メモリを使った並列処理をするときのメモ
Pythonで大きめのデータ(数百M~数GB)を解析しようとしたときの技術メモ。
Pythonを並列処理で高速化しようとするとGILの制約を受ける。この問題は複数の同一プロセスを起動することで回避できるが、巨大なデータを扱う場合、それが読出専用であってもメモリを浪費する。
Pythonのmultiprocessingモジュールには共有メモリを扱うためのRawArray/Array等のクラスがある。これを使えば複数のプロセスで同一メモリを共有できるが、それなりのオーバーヘッドが予測されるので計測することにした。
計測方法
100M個のデータ(float)の合計をN回求めるコードを、メモリの種別/マルチプロセスの有無ごとに実装して、処理時間を計測する。メモリは読み出し専用とする。
以下はローカルメモリでシングルプロセスの場合。
src=[0]*100*1024*1024 def singleList(s,l=1): print("single+List") #for加算 start = time.time() for c in range(l): ret=0 for i in s: ret=ret+i return time.time()-start singleList
バリエーション
- シングルプロセス+Python配列
- シングルプロセス+numpy配列
- シングルプロセス+RawSharedArray配列
シングルプロセス+SharedArray配列- マルチプロセス+Python配列
- マルチプロセス+numpy配列
- マルチプロセス+RawSharedArray配列
- SharedArrayは時間がかかりすぎるためテストから除外した。
- numpy配列、RawSharedArray配列、デコード時間のみを計測対象とする。
- numpy配列はオブジェクトのインデクス参照速度の基準として計測。
実行結果
連続4回実行、または4プロセスで1回した結果。
single+List 10.10sec single+NumPy 77.74sec single+sharedRawArray 69.94sec MP+sharedRawArray+List 54.34sec MP+sharedRawArray+NumPy 35.79sec MP+sharedRawArray 32.66sec
考察
シングルプロセス
シングルプロセス+python配列が一番高速。配列アクセスが大部分を占めるコードなので仕方ない。
NumPy、SharedRawArrayは7倍程度のアクセスコストがかかる。なんてこった。
だがNumPyとSharedRawArrayは同程度速度であるから、ロックなどの余計なコストはかかっていないように見える。
マルチプロセス
マルチスレッドの場合でも、シングルプロセスの速度を超えることはなかった。しかし、シングルプロセス+RawSharedArray配列と比較すると、MP+sharedRawArrayではマルチプロセスの恩恵がある。(期待値4のはずが2倍どまりではあるが。)
処理時間全体に占める配列アクセス部分を少なくして様子を見る必要がありそうだ。
マルチプロセス内でsharedRawArrayをListに変換するとパフォーマンスが悪化している。これはデータを複製しているためで、実用的ではない。
結論
マルチプロセス+numpy配列、またはマルチプロセス+RawSharedArray配列を使用するのがよさそうだ。ただし、2回以上アクセスする場合は一度ローカル変数に保存するなどの対処が必要だろう。