web-dev-qa-db-ja.com

マルチプロセッシングプロセス間で大きな読み取り専用のNumpy配列を共有する

60GBのSciPyアレイ(マトリックス)を持っています。5個以上のmultiprocessingProcessオブジェクト間で共有する必要があります。私はnumpy-sharedmemを見て、SciPyリストで この議論 を読みました。 numpy-sharedmemmultiprocessing.RawArray()を使用し、NumPy dtypesをctypesにマッピングする2つのアプローチがあるようです。現在、numpy-sharedmemが方法のように見えますが、良い参考例をまだ見ていません。配列(実際には行列)は読み取り専用になるため、ロックは必要ありません。今、そのサイズのために、私はコピーを避けたいです。 のように聞こえます正しい方法は、onlyのコピーを作成することですsharedmem配列として配列し、Processオブジェクトに渡しますか?いくつかの具体的な質問:

  1. Sharedmemハンドルを実際にsub-Process() esに渡す最良の方法は何ですか? 1つの配列を渡すためだけにキューが必要ですか?パイプの方が良いでしょうか? Process()サブクラスのinitに引数として渡すことはできますか(ピクルスされていると想定しています)。

  2. 上記でリンクしたディスカッションで、numpy-sharedmemが64ビットセーフではないという言及がありますか?私は間違いなく、32ビットでアドレス指定できない構造を使用しています。

  3. RawArray()アプローチにはトレードオフがありますか?遅くて、バグが多い?

  4. Numpy-sharedmemメソッドのctypeからdtypeへのマッピングは必要ですか?

  5. これを行うOpenSourceコードの例はありますか?私は非常に実践的な知識を持っているので、見栄えの良い例がなければこれを機能させるのは困難です。

これを他の人にわかりやすくするために提供できる追加情報がある場合は、コメントして追加してください。ありがとう!

これは、Ubuntu LinuxおよびMaybeMac OSで実行する必要がありますが、移植性は大きな問題ではありません。

77
Will

@Velimir Mlakerがすばらしい答えをくれました。少しコメントと小さな例を追加できると思いました。

(sharedmemに関する多くのドキュメントを見つけることができませんでした-これらは私自身の実験の結果です。)

  1. サブプロセスの開始時に、または開始後にハンドルを渡す必要がありますか?前者の場合は、targetargsおよびProcess引数を使用できます。これは、グローバル変数を使用するよりも潜在的に優れています。
  2. リンクしたディスカッションページから、64ビットLinuxのサポートがしばらく前にsharedmemに追加されたように見えるので、問題ではない可能性があります。
  3. これについては知りません。
  4. いいえ。以下の例を参照してください。

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.Rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __== '__main__':
    split_work(4)

出力

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

この 関連する質問 は役に立つかもしれません。

26
James Lim

Linux(またはPOSIX準拠のシステム)を使用している場合、この配列をグローバル変数として定義できます。 Linuxでは、multiprocessingが新しい子プロセスを開始するときにfork()を使用しています。新しく生成された子プロセスは、変更しない限り自動的に親とメモリを共有します( copy-on-write メカニズム)。

「配列(実際にはマトリックス)は読み取り専用であるため、いかなる種類のロックも必要ありません」と言っているため、この動作を利用することは非常にシンプルでありながら非常に効率的なアプローチです。すべての子プロセスがアクセスしますこの大きなnumpy配列を読み取るときに、物理メモリ内の同じデータ。

配列をProcess()コンストラクターに渡さないでください。これは、multiprocessingからpickleに子にデータを指示します。これは、非常に非効率的または不可能です。 Linuxでは、fork()の直後に、子は同じ物理メモリを使用する親の正確なコピーであるため、必要なことはPython変数 'を含む行列は、Process()に渡すtarget関数内からアクセスできます。これは通常、 'global'変数で実現できます。

サンプルコード:

_from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __== "__main__":
    main()
_

Windows-fork()をサポートしない-multiprocessingはwin32 API呼び出しCreateProcessを使用しています。任意の実行可能ファイルからまったく新しいプロセスを作成します。そのため、Windowsでは、親の実行時に作成されたデータが必要な場合、子にデータをピクルするためにrequiredが必要です。

32

私が書いた小さなコードに興味があるかもしれません: github.com/vmlaker/benchmark-sharedmem

対象のファイルは_main.py_のみです。 numpy-sharedmem のベンチマークです。コードは、配列(numpyまたはsharedmemのいずれか)を、Pipeを介して生成されたプロセスに渡すだけです。ワーカーはデータに対してsum()を呼び出すだけです。 2つの実装間のデータ通信時間の比較にのみ興味がありました。

また、より複雑な別のコードを作成しました: github.com/vmlaker/sherlock

ここでは、OpenCVを使用したリアルタイムの画像処理に numpy-sharedmem モジュールを使用します。画像は、OpenCVの新しい_cv2_ APIに従って、NumPy配列です。実際にその参照である画像は、 _multiprocessing.Manager_ から作成されたディクショナリオブジェクトを介してプロセス間で共有されます(キューまたはパイプを使用するのとは対照的です)。プレーンなNumPy配列。

パイプとキュー

私の経験では、IPC Pipeの方がキューより高速です。キューは複数のプロデューサー/コンシューマーに対して安全にするためにロックを追加するため、それは理にかなっています。パイプはありません。 2つのプロセスが行き来しており、Pipeを使用しても安全です。または、ドキュメントを読むと:

...パイプの異なる端を同時に使用するプロセスによる破損のリスクはありません。

sharedmem安全性

sharedmemモジュールの主な問題は、プログラムの異常終了時にメモリリークが発生する可能性があることです。これは、長い議論 ここ で説明されています。 2011年4月10日、Sturlaはメモリリークの修正について言及していますが、それ以来、GitHubでSturla Molden独自のリポジトリ( github.com/sturlamolden/sharedmem-numpy )とクリス・リー・メッサーのBitbucket( bitbucket.org/cleemesser/numpy-sharedmem )。

20
Velimir Mlaker

配列がこれほど大きい場合は、numpy.memmapを使用できます。たとえば、'test.array'などのディスクに配列を保存している場合、「書き込み」モードでも同時プロセスを使用してそのデータにアクセスできますが、「読み取り」モードのみが必要なため、ケースは簡単です。

配列の作成:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

その後、通常の配列と同じ方法でこの配列を埋めることができます。例えば:

a[:10,:100]=1.
a[10:,100:]=2.

変数aを削除すると、データはディスクに保存されます。

後で、test.arrayのデータにアクセスする複数のプロセスを使用できます。

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

関連する回答:

11

pyro のドキュメントをご覧になると便利です。タスクを適切にパーティション分割できる場合は、それを使用して、同じマシンの異なるコアや異なるコアで異なるセクションを実行できます。機械。

3
Steve Barnes