Pythonで並列プログラミングを行う方法は?


141

C ++の場合、OpenMPを使用して並列プログラミングを行うことができます。ただし、OpenMPはPythonでは機能しません。Pythonプログラムの一部を並列化したい場合はどうすればよいですか?

コードの構造は次のように考えることができます:

solve1(A)
solve2(B)

どこsolve1solve2二つの独立した機能です。実行時間を短縮するために、この種のコードを順番にではなく並行して実行するにはどうすればよいですか?誰かが私を助けてくれることを願っています。よろしくお願いします。コードは次のとおりです。

def solve(Q, G, n):
    i = 0
    tol = 10 ** -4

    while i < 1000:
        inneropt, partition, x = setinner(Q, G, n)
        outeropt = setouter(Q, G, n)

        if (outeropt - inneropt) / (1 + abs(outeropt) + abs(inneropt)) < tol:
            break

        node1 = partition[0]
        node2 = partition[1]

        G = updateGraph(G, node1, node2)

        if i == 999:
            print "Maximum iteration reaches"
    print inneropt

ここで、setinnerとsetouterは2つの独立した関数です。それは私が平行したいところです...


31
マルチプロセッシングを見てください。注:Pythonのスレッドは、CPUバウンドタスクには適しておらず、I / Oバウンドにのみ適しています。
9000

4
@ 9000 +100インターネット。CPUとI / Oに依存するタスクについて言及しています。
Hyperboreus

@ 9000実際、私の知る限り、スレッドはCPUにバインドされたタスクにはまったく適していません。プロセスは、実際のCPUにバインドされたタスクを実行するときの方法です。
Omar Al-Ithawi 2013

6
@OmarIthawi:なぜ、多くのCPUコアがある場合、スレッドは正常に動作します(今のところ通常)。次に、プロセスはいくつかのスレッドを実行してこれらのすべてのコアを並列にロードし、それらの間で共通のデータを暗黙的に共有します(つまり、明示的な共有メモリ領域やプロセス間メッセージングがない場合)。
9000

1
@ user2134774:ええ、はい、私の2番目のコメントはほとんど意味がありません。おそらく、GILをリリースする唯一のC拡張は、その恩恵を受けることができます。たとえば、NumPyとPandasの一部がそれを行います。他の場合には、それは間違っています(しかし、今は編集できません)。
9000

回答:


162

マルチプロセッシングモジュールを使用できます。この場合、処理プールを使用する場合があります。

from multiprocessing import Pool
pool = Pool()
result1 = pool.apply_async(solve1, [A])    # evaluate "solve1(A)" asynchronously
result2 = pool.apply_async(solve2, [B])    # evaluate "solve2(B)" asynchronously
answer1 = result1.get(timeout=10)
answer2 = result2.get(timeout=10)

これにより、一般的な作業を実行できるプロセスが生成されます。を渡さなかったprocessesため、マシン上の各CPUコアに対して1つのプロセスが生成されます。各CPUコアは、1つのプロセスを同時に実行できます。

リストを単一の関数にマップする場合は、次のようにします。

args = [A, B]
results = pool.map(solve1, args)

GILはPythonオブジェクトに対する操作をロックするため、スレッドを使用しないでください。


1
pool.mapも引数として辞書を受け入れますか?または単純なリストだけですか?
Bndr 2015年

ただリストだと思います。ただし、キー値のタプルのリストとなるdict.items()を渡すだけです
Matt Williamson

残念ながら、これは `unhashable type: 'list'`エラーで終わります
Bndr

私の最後のコメントに加えて: `dict.items()`は機能します。変数の処理を変更する必要があったため、エラーが発生します。残念ながら、エラーメッセージはあまり役に立ちませんでした...だから:ヒントをありがとう :-)
Bndr 2015年

2
ここでタイムアウトとは何ですか?
ガンマ

26

これはRayで非常にエレガントに行うことができます。

サンプルを並列化するには、@ray.remoteデコレータを使用して関数を定義してから、を使用してそれらを呼び出す必要があり.remoteます。

import ray

ray.init()

# Define the functions.

@ray.remote
def solve1(a):
    return 1

@ray.remote
def solve2(b):
    return 2

# Start two tasks in the background.
x_id = solve1.remote(0)
y_id = solve2.remote(1)

# Block until the tasks are done and get the results.
x, y = ray.get([x_id, y_id])

マルチプロセッシングモジュールに比べて、これには多くの利点があります。

  1. 同じコードがマルチコアマシンとマシンのクラスターで実行されます。
  2. プロセスは、共有メモリとゼロコピーシリアライゼーションを通じてデータを効率的に共有します
  3. エラーメッセージは適切に伝播されます。
  4. これらの関数呼び出しは一緒に構成できます。

    @ray.remote
    def f(x):
        return x + 1
    
    x_id = f.remote(1)
    y_id = f.remote(x_id)
    z_id = f.remote(y_id)
    ray.get(z_id)  # returns 4
  5. 関数をリモートで呼び出すことに加えて、クラスはリモートでアクターとしてインスタンス化できます。

レイは、私が開発を支援してきたフレームワークであることに注意してください。


Pythonでパッケージをインストールしようとすると、「rayの要件を満たすバージョンを見つけることができませんでした()から一致するディストリビューションが見つかりませんでした」というエラーが表示され続ける
alwaysaskingquestions

2
通常、この種のエラーは、アップグレードする必要があることを意味しますpip。試してみることをお勧めしpip install --upgrade pipます。sudoまったく使用する必要がpipある場合は、インストールに使用しrayているバージョンが、アップグレードされるバージョンと同じでない可能性があります。で確認できpip --versionます。また、Windowsは現在サポートされていないため、Windowsを使用している場合はおそらく問題です。
ロバート西原

1
これは主に、複数のマシンに同時にジョブを分散するためのものです。
マットウィリアムソン

2
実際には、単一マシンの場合とクラスター設定の両方に対して最適化されています。多くの設計上の決定(共有メモリ、ゼロコピーシリアル化など)は、単一のマシンを適切にサポートすることを目的としています。
ロバート西原

2
ドキュメントがそれをもっと指摘してくれたら素晴らしいです。私はドキュメントを読んで、それが実際には単一のマシンのケースを対象としていないことを理解しました。
スレッジ

4

CPythonは、グローバルインタープリターロックを使用して、並列プログラミングをC ++よりも少し面白くしています

このトピックには、いくつかの役立つ例と課題の説明があります。

Linuxでタスクセットを使用するマルチコアシステムでのPythonグローバルインタープリターロック(GIL)の回避策


13
実際にコードを同時に実行できないことを「興味深い」と呼んでいますか?:-/
ManuelSchneid3r

4

他の人が言ったように、解決策は複数のプロセスを使用することです。ただし、どちらのフレームワークがより適切であるかは、多くの要因に依存します。すでに述べたものに加えて、charm4pympi4py(私はcharm4pyの開発者です)もあります。

上記の例を実装するには、ワーカープールの抽象化を使用するよりも効率的な方法があります。メインループGは、1000回の反復ごとに同じパラメーター(完全なグラフを含む)をワーカーに繰り返し送信します。少なくとも1つのワーカーが別のプロセスに常駐するため、これには、引数をコピーして他のプロセスに送信することが含まれます。これは、オブジェクトのサイズによっては非常にコストがかかる可能性があります。代わりに、ワーカーに状態を保存させ、更新された情報を送信するだけで十分です。

たとえば、charm4pyでは、次のように実行できます。

class Worker(Chare):

    def __init__(self, Q, G, n):
        self.G = G
        ...

    def setinner(self, node1, node2):
        self.updateGraph(node1, node2)
        ...


def solve(Q, G, n):
    # create 2 workers, each on a different process, passing the initial state
    worker_a = Chare(Worker, onPE=0, args=[Q, G, n])
    worker_b = Chare(Worker, onPE=1, args=[Q, G, n])
    while i < 1000:
        result_a = worker_a.setinner(node1, node2, ret=True)  # execute setinner on worker A
        result_b = worker_b.setouter(node1, node2, ret=True)  # execute setouter on worker B

        inneropt, partition, x = result_a.get()  # wait for result from worker A
        outeropt = result_b.get()  # wait for result from worker B
        ...

この例では、実際に必要なワーカーは1つだけです。メインループは関数の1つを実行し、ワーカーに他の関数を実行させることができます。しかし、私のコードはいくつかのことを説明するのに役立ちます:

  1. ワーカーAはプロセス0で実行されます(メインループと同じ)。result_a.get()結果を待ってブロックされている間、ワーカーAは同じプロセスで計算を行います。
  2. 引数はワーカーAへの参照によって自動的に渡されます。ワーカーAは同じプロセス内にあるためです(コピーは含まれません)。

2

場合によっては、Numbaを使用してループを自動的に並列化することが可能ですが、Pythonの小さなサブセットでのみ機能します。

from numba import njit, prange

@njit(parallel=True)
def prange_test(A):
    s = 0
    # Without "parallel=True" in the jit-decorator
    # the prange statement is equivalent to range
    for i in prange(A.shape[0]):
        s += A[i]
    return s

残念ながら、NumbaはNumpy配列でのみ機能し、他のPythonオブジェクトでは機能しないようです。理論的には、それにもできる可能性がありますC ++にはPythonをコンパイルして、自動的にインテルC ++コンパイラーを使用して、それを並列私はまだこれを試していないが、。


2

joblibライブラリを使用して、並列計算とマルチプロセッシングを実行できます。

from joblib import Parallel, delayed

foo並列実行したい関数を作成するだけで、次のコードに基づいて並列処理を実装できます。

output = Parallel(n_jobs=num_cores)(delayed(foo)(i) for i in input)

次のnum_coresようにmultiprocessingライブラリから入手できる場所:

import multiprocessing

num_cores = multiprocessing.cpu_count()

複数の入力引数を持つ関数があり、引数の1つをリストで反復したい場合は、次のようにライブラリのpartial関数を使用できfunctoolsます。

from joblib import Parallel, delayed
import multiprocessing
from functools import partial
def foo(arg1, arg2, arg3, arg4):
    '''
    body of the function
    '''
    return output
input = [11,32,44,55,23,0,100,...] # arbitrary list
num_cores = multiprocessing.cpu_count()
foo_ = partial(foo, arg2=arg2, arg3=arg3, arg4=arg4)
# arg1 is being fetched from input list
output = Parallel(n_jobs=num_cores)(delayed(foo_)(i) for i in input)

pythonとRマルチプロセッシングの完全な説明は、ここにいくつかの例があります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.