web-dev-qa-db-ja.com

Parallelise python numpy配列と共有メモリを使用したループ

このトピックに関するいくつかの質問と回答を知っていますが、この特定の問題に対する満足のいく回答は見つかりませんでした。

Numpy配列がnumpy/scipy関数を介して操作されるpythonループの単純な共有メモリ並列化を行う最も簡単な方法は何ですか?

私は最も効率的な方法を探していません。ループが並列に実行されていないときに大幅な書き換えを必要としない、実装が簡単なものが欲しかったのです。 OpenMPが低水準言語で実装するのと同じように。

この点に関して私が見た最良の答えは これ ですが、これはかなり不格好な方法であり、単一の引数、数行の共有配列をとる関数にループを表現する必要がありますCrudを変換するには、並列関数を__main__から呼び出す必要があるようですが、インタラクティブなプロンプト(多くの時間を費やしている)からはうまく機能しないようです。

Pythonの単純さのすべてで、これはループを並列化するための本当に最良の方法ですか?本当に?これは、OpenMP方式で並列化するのは簡単なことです。

マルチプロセッシングモジュールの不透明なドキュメントを注意深く読んだのですが、それが非常に一般的であるため、単純なループ並列化以外のすべてに適しているように思われることがわかりました。マネージャー、プロキシ、パイプなどの設定には興味がありません。タスク間の通信がない完全並列の単純なループがあります。 MPIを使用してこのような単純な状況を並列化するのはやり過ぎのように思えますが、この場合はメモリ効率が悪いことは言うまでもありません。

Python用のさまざまな共有メモリ並列パッケージについて学ぶ時間がありませんでしたが、誰かがこれについてもっと経験があり、もっと簡単な方法を教えてくれるかどうか疑問に思っていました。 Cython(私はすでに使用しています)などのシリアル最適化手法や、BLAS(私の場合はより一般的でより並列)などの並列numpy/scipy関数の使用を提案しないでください。

24
tiago

Cython並列サポートあり:

# asd.pyx
from cython.parallel cimport prange

import numpy as np

def foo():
    cdef int i, j, n

    x = np.zeros((200, 2000), float)

    n = x.shape[0]
    for i in prange(n, nogil=True):
        with gil:
            for j in range(100):
                x[i,:] = np.cos(x[i,:])

    return x

2コアマシンの場合:

$ cython asd.pyx
$ gcc -fPIC -fopenmp -shared -o asd.so asd.c -I/usr/include/python2.7
$ export OMP_NUM_THREADS=1
$ time python -c 'import asd; asd.foo()'
real    0m1.548s
user    0m1.442s
sys 0m0.061s

$ export OMP_NUM_THREADS=2
$ time python -c 'import asd; asd.foo()'
real    0m0.602s
user    0m0.826s
sys 0m0.075s

np.cos(他のufuncと同様)がGILを解放するため、これは並行して正常に実行されます。

これをインタラクティブに使用したい場合:

# asd.pyxbdl
def make_ext(modname, pyxfilename):
    from distutils.extension import Extension
    return Extension(name=modname,
                     sources=[pyxfilename],
                     extra_link_args=['-fopenmp'],
                     extra_compile_args=['-fopenmp'])

および(最初に​​asd.soおよびasd.cを削除します):

>>> import pyximport
>>> pyximport.install(reload_support=True)
>>> import asd
>>> q1 = asd.foo()
# Go to an editor and change asd.pyx
>>> reload(asd)
>>> q2 = asd.foo()

そうです、場合によっては、スレッドを使用するだけで並列化できます。 OpenMPはスレッド化のための単なる豪華なラッパーであるため、Cythonは構文を簡単にするためにここでのみ必要です。 Cythonがなければ、threadingモジュールを使用できます---マルチプロセッシングと同様に(そしておそらくより堅牢に)機能しますが、配列を共有メモリとして宣言するために特別なことをする必要はありません。

ただし、すべての操作でGILが解放されるわけではないため、パフォーマンスのためにYMMVを使用します。

***

そして、他のStackoverflowの回答から削り取られた別のおそらく有用なリンク---マルチプロセッシングへの別のインターフェース: http://packages.python.org/joblib/parallel.html

18
pv.

マッピング操作(この場合はmultiprocessing.Pool.map())を使用することは、多かれ少なかれ、単一のマシンでループを並列化するための標準的な方法です。組み込みのmap()が並列化されない限り。

さまざまな可能性の概要を見つけることができます ここ

openmp with python (またはむしろcython)を使用できますが、正確に簡単に見えるわけではありません。

IIRC、___main___からマルチプロセッシングのものだけを実行する場合のポイントは、Windowsとの互換性のために必要です。 Windowsにはfork()がないため、新しいpythonインタープリターを起動し、その中にコードをインポートする必要があります。

編集

Numpyは、dot()vdot()innerproduct()などの一部の操作を、たとえば次のような優れたマルチスレッドBLASライブラリで構成すると並列化できます。 OpenBLAS 。 ( この質問 も参照してください。)

Numpy配列操作はほとんど要素ごとであるため、それらを並列化することは可能のようです。ただし、これには、_multiprocessing.Pool_とは異なり、pythonオブジェクトの共有メモリセグメントを設定するか、配列を分割してさまざまなプロセスにフィードすることが含まれます。いいえどのアプローチを採用しても、それらすべてを管理するにはメモリと処理のオーバーヘッドが発生します。大規模なテストを実行して、実際に努力する価値のあるアレイのサイズを確認する必要があります。これらのテストの結果は、おそらくハードウェアアーキテクチャ、オペレーティングシステム、およびRAMの容量。

2
Roland Smith

。map()ParallelRegression のmathDict()クラスのメソッドは、インタラクティブなプロンプトで非常に簡単な2行のコードで探していることを正確に実行します。真のマルチプロセッシングを使用するため、並列で実行する関数がピクル可能であるという要件は避けられませんが、これにより、複数のプロセスから共有メモリ内のマトリックスをループする簡単な方法が提供されます。

ピクルス可能な機能があるとしましょう:

def sum_row( matrix, row ):
    return( sum( matrix[row,:] ) )

次に、それを表すmathDict()オブジェクトを作成し、mathDict()。map()を使用する必要があります。

matrix = np.array( [i for i in range( 24 )] ).reshape( (6, 4) )

RA, MD = mathDictMaker.fromMatrix( matrix, integer=True )
res = MD.map( [(i,) for i in range( 6 )], sum_row, ordered=True )

print( res )
# [6, 22, 38, 54, 70, 86]

ドキュメント(上記のリンク)では、任意の位置の行列自体を含め、またはキーワード引数として、位置引数とキーワード引数の組み合わせを関数に渡す方法について説明しています。これにより、すでに作成したほとんどすべての関数を変更せずに使用できるようになります。

0
RichardB