ネコと和解せよ

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

バリエーション

  1. シングルプロセス+Python配列
  2. シングルプロセス+numpy配列
  3. シングルプロセス+RawSharedArray配列
  4. シングルプロセス+SharedArray配列
  5. マルチプロセス+Python配列
  6. マルチプロセス+numpy配列
  7. マルチプロセス+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回以上アクセスする場合は一度ローカル変数に保存するなどの対処が必要だろう。