単純なPythonループを並列化するにはどうすればよいですか?


255

これはおそらく些細な質問ですが、Pythonで次のループを並列化するにはどうすればよいですか?

# setup output lists
output1 = list()
output2 = list()
output3 = list()

for j in range(0, 10):
    # calc individual parameter value
    parameter = j * offset
    # call the calculation
    out1, out2, out3 = calc_stuff(parameter = parameter)

    # put results into correct output list
    output1.append(out1)
    output2.append(out2)
    output3.append(out3)

Pythonでシングルスレッドを開始する方法は知っていますが、結果を「収集」する方法がわかりません。

複数のプロセスも問題ありません-この場合に最も簡単なものは何でも。現在Linuxを使用していますが、コードはWindowsとMacでも動作するはずです。

このコードを並列化する最も簡単な方法は何ですか?

回答:


193

CPythonで複数のスレッドを使用しても、グローバルインタープリターロック(GIL)により、純粋なPythonコードのパフォーマンスは向上しません。multiprocessing代わりにモジュールを使用することをお勧めします:

pool = multiprocessing.Pool(4)
out1, out2, out3 = zip(*pool.map(calc_stuff, range(0, 10 * offset, offset)))

これは対話型インタープリターでは機能しないことに注意してください。

GILに関する通常のFUDを回避するには:いずれにしても、この例でスレッドを使用してもメリットはありません。あなたはしたい、彼らは問題の全体の束を避けるため、ここでは、スレッドではなくプロセスを使用します。


46
これが選ばれた答えなので、より包括的な例を示すことは可能ですか?の議論はcalc_stuff何ですか?
Eduardo Pignatelli

2
@EduardoPignatelli multiprocessingより包括的な例については、モジュールのドキュメントをお読みください。 Pool.map()基本的にのようmap()に動作しますが、並行して動作します。
Sven Marnach

3
このコード構造にtqdmロードバーを単純に追加する方法はありますか?私はtqdm(pool.imap(calc_stuff、range(0、10 * offset、offset)))を使用しましたが、完全なロードバーグラフィックが表示されません。
user8188120 2018

@ user8188120 tqdmについて聞いたことがないので、申し訳ありませんが、私はそれを手伝うことができません。
Sven Marnach

tqdmローディングバーのためにこの質問を参照してください。stackoverflow.com/questions/41920124/...
ヨハネス

67

単純なforループを並列化するために、joblibはマルチプロセッシングの生の使用に多くの価値をもたらします。短い構文だけでなく、(オーバーヘッドを削除するために)非常に高速な場合の透過的な反復のバンチングや、子プロセスのトレースバックのキャプチャなど、より適切なエラー報告を行うこともできます。

免責事項:私はjoblibの最初の作者です。


1
私はjupyterでjoblibを試しましたが、動作しません。Parallel-delayed呼び出しの後、ページは機能しなくなりました。
Jie

1
こんにちは、私はjoblib(stackoverflow.com/questions/52166572/…)の使用に問題があります。原因が何かの手掛かりはありますか?どうもありがとう。
Ting Sun

撮りたいものみたい!二重ループでそれを使用することは可能ですか?たとえば、範囲(10)のfor i:範囲(20)のfor j
CutePoison

51

このコードを並列化する最も簡単な方法は何ですか?

私はconcurrent.futuresこれが本当に好きで、バージョン3.2以降の Python3でそしてPyPiの 2.6と2.7へのバックポート経由で利用できます。

スレッドまたはプロセスを使用して、まったく同じインターフェースを使用できます。

マルチプロセッシング

これをファイルに入れます-futuretest.py:

import concurrent.futures
import time, random               # add some random sleep time

offset = 2                        # you don't supply these so
def calc_stuff(parameter=None):   # these are examples.
    sleep_time = random.choice([0, 1, 2, 3, 4, 5])
    time.sleep(sleep_time)
    return parameter / 2, sleep_time, parameter * parameter

def procedure(j):                 # just factoring out the
    parameter = j * offset        # procedure
    # call the calculation
    return calc_stuff(parameter=parameter)

def main():
    output1 = list()
    output2 = list()
    output3 = list()
    start = time.time()           # let's see how long this takes

    # we can swap out ProcessPoolExecutor for ThreadPoolExecutor
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for out1, out2, out3 in executor.map(procedure, range(0, 10)):
            # put results into correct output list
            output1.append(out1)
            output2.append(out2)
            output3.append(out3)
    finish = time.time()
    # these kinds of format strings are only available on Python 3.6:
    # time to upgrade!
    print(f'original inputs: {repr(output1)}')
    print(f'total time to execute {sum(output2)} = sum({repr(output2)})')
    print(f'time saved by parallelizing: {sum(output2) - (finish-start)}')
    print(f'returned in order given: {repr(output3)}')

if __name__ == '__main__':
    main()

そしてここに出力があります:

$ python3 -m futuretest
original inputs: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
total time to execute 33 = sum([0, 3, 3, 4, 3, 5, 1, 5, 5, 4])
time saved by parallellizing: 27.68999981880188
returned in order given: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

マルチスレッド

次にに変更ProcessPoolExecutorThreadPoolExecutor、モジュールを再度実行します。

$ python3 -m futuretest
original inputs: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
total time to execute 19 = sum([0, 2, 3, 5, 2, 0, 0, 3, 3, 1])
time saved by parallellizing: 13.992000102996826
returned in order given: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

これで、マルチスレッドとマルチプロセッシングの両方が完了しました!

パフォーマンスと両方を一緒に使用することに注意してください。

サンプリングは小さすぎて結果を比較できません。

ただし、Windowsはフォークをサポートしていないため、新しいプロセスを起動するまでに時間がかかるため、マルチスレッドは一般的にマルチプロセッシングよりも高速になると思います。LinuxやMacでは、おそらくもっと近くなるでしょう。

複数のスレッドを複数のプロセス内にネストできますが、複数のスレッドを使用して複数のプロセスをスピンオフしないことをお勧めします。


ThreadPoolExecutorはGILによって課された制限をバイパスしますか?また、executorが終了するのを待つためにjoin()を実行する必要はありませんか?これは、コンテキストマネージャー内で暗黙的に処理されます
PirateApp

1
ノー、ノー、イエスに「暗黙的に扱わ」
アーロン・ホール

何らかの理由で、問題をスケールアップするとき、マルチスレッドは非常に高速ですが、マルチプロセッシングは(macOSで)スタックしたプロセスの束を生成します。なぜそうなるのでしょうか?プロセスにはネストされたループと数学のみが含まれ、特別なものは何もありません。
komodovaran_

@komodovaran_プロセスは、1つずつ完全なPythonプロセスですが、スレッドは、プロセス、そのバイトコード、および他のすべてのスレッドとメモリ内にある他のすべてを共有する独自のスタックを持つ実行のスレッドです?
アーロンホール

49
from joblib import Parallel, delayed
import multiprocessing

inputs = range(10) 
def processInput(i):
    return i * i

num_cores = multiprocessing.cpu_count()

results = Parallel(n_jobs=num_cores)(delayed(processInput)(i) for i in inputs)
print(results)

上記は私のマシンで美しく動作します(Ubuntu、パッケージjoblibはプレインストールされていますが、を介してインストールできますpip install joblib)。

https://blog.dominodatalab.com/simple-parallelization/から取得


3
私はあなたのコードを試しましたが、私のシステムでは、このコードの順次バージョンは約30分で、上記の並列バージョンは4分かかります。なんでそうなの?
shaifali Gupta

3
ご回答有難うございます!これは2019
。– Heikki Pulkkinen

2
マルチプロセッシングはPython 3.xでは無効であるため、これは私には機能しません。
EngrStudent

2
@EngrStudent「無効」という意味がわからない。Python 3.6.xで動作します。
tyrex

@tyrex共有ありがとうございます!このjoblibパッケージは素晴らしく、サンプルは私にとってはうまくいきます。しかし、もっと複雑な状況では、残念ながらバグがありました。github.com/joblib/joblib/issues/949
Open Food Broker、

13

Rayを使用することにはいくつかの利点があります。

  • 複数のコア(同じコード)に加えて、複数のマシンで並列化できます。
  • 共有メモリ(およびゼロコピーシリアル化)による数値データの効率的な処理。
  • 分散スケジューリングによる高いタスクスループット。
  • フォールトトレランス。

あなたの場合、Rayを起動してリモート関数を定義することができます

import ray

ray.init()

@ray.remote(num_return_vals=3)
def calc_stuff(parameter=None):
    # Do something.
    return 1, 2, 3

そしてそれを並行して呼び出します

output1, output2, output3 = [], [], []

# Launch the tasks.
for j in range(10):
    id1, id2, id3 = calc_stuff.remote(parameter=j)
    output1.append(id1)
    output2.append(id2)
    output3.append(id3)

# Block until the results have finished and get the results.
output1 = ray.get(output1)
output2 = ray.get(output2)
output3 = ray.get(output3)

クラスタで同じ例を実行する場合、変更されるのはray.init()の呼び出しだけです。関連ドキュメントはここにあります

レイの開発を支援していることに注意してください。


1
rayを検討している人にとっては、それがWindowsをネイティブでサポートしていないことを知っていることは重要です。WSL(Linuxの場合はWindowsサブシステム)を使用してWindowsで機能させるためのハッキングが可能ですが、Windowsを使用する場合は、すぐに使用できます。
OscarVanL

9

これが最も簡単な方法です。

asyncioを使用できます。(ドキュメントはここにあります)。これは、高性能ネットワークとWebサーバー、データベース接続ライブラリ、分散タスクキューなどを提供する複数のPython非同期フレームワークの基盤として使用されます。さらに、あらゆる種類の問題に対応するための高レベルAPIと低レベルAPIの両方を備えています。

import asyncio

def background(f):
    def wrapped(*args, **kwargs):
        return asyncio.get_event_loop().run_in_executor(None, f, *args, **kwargs)

    return wrapped

@background
def your_function(argument):
    #code

これで、この関数は、メインプログラムを待機状態にせずに呼び出されるたびに並列で実行されます。これを使用してforループを並列化することもできます。forループで呼び出された場合、ループは順次ですが、インタプリタがそこに到着するとすぐに、すべての反復がメインプログラムと並行して実行されます。 例えば:

@background
def your_function(argument):
    time.sleep(5)
    print('function finished for '+str(argument))


for i in range(10):
    your_function(i)


print('loop finished')

これにより、次の出力が生成されます。

loop finished
function finished for 4
function finished for 8
function finished for 0
function finished for 3
function finished for 6
function finished for 2
function finished for 5
function finished for 7
function finished for 9
function finished for 1

私はにタイプミスがあると思いますwrapped()し、それがあるべき**kwargs代わりの*kwargs
ヤクブ・olczyk

おっとっと!私の間違い。修正しました!
User5

6

スレッドと1つのmutexを使用して1つのグローバルリストを保護しないのはなぜですか。

import os
import re
import time
import sys
import thread

from threading import Thread

class thread_it(Thread):
    def __init__ (self,param):
        Thread.__init__(self)
        self.param = param
    def run(self):
        mutex.acquire()
        output.append(calc_stuff(self.param))
        mutex.release()   


threads = []
output = []
mutex = thread.allocate_lock()

for j in range(0, 10):
    current = thread_it(j * offset)
    threads.append(current)
    current.start()

for t in threads:
    t.join()

#here you have output list filled with data

覚えておいてください、あなたはあなたの最も遅いスレッドと同じくらい速くなります


2
私はこれが非常に古い回答であることを知っているので、どこからでもランダムな反対投票をするのは残念です。スレッドは何も並列化しないため、反対票を投じただけです。Pythonのスレッドは、グローバルインタープリターロックのため、一度にインタープリターで実行される1つのスレッドにのみバインドされるため、同時プログラミングをサポートしますが、 OPが要求しているため、並列ではありません
skrrgwasme 2017

3
@skrrgwasme私はあなたがこれを知っていることを知っていますが、「彼らは何も並列化しない」という言葉を使うと、読者を誤解させるかもしれません。操作がIOバインドされているため、またはイベントを待機している間スリープしているために操作に時間がかかる場合、インタープリターは解放されて他のスレッドを実行するため、このような場合に人々が望んでいる速度の向上につながります。CPUにバインドされたスレッドのみが、実際にskrrgwasmeの発言の影響を受けます。
Jonathan Hartley

5

joblibはとても役に立ちました。次の例をご覧ください。

from joblib import Parallel, delayed
def yourfunction(k):   
    s=3.14*k*k
    print "Area of a circle with a radius ", k, " is:", s

element_run = Parallel(n_jobs=-1)(delayed(yourfunction)(k) for k in range(1,10))

n_jobs = -1:利用可能なすべてのコアを使用


14
自分で投稿する前に、既存の回答を確認することをお勧めします。この回答は、の使用も提案していますjoblib
sanyash

2

非同期関数があるとしましょう

async def work_async(self, student_name: str, code: str, loop):
"""
Some async function
"""
    # Do some async procesing    

大規模なアレイで実行する必要があります。一部の属性はプログラムに渡され、一部は配列内のディクショナリ要素のプロパティから使用されます。

async def process_students(self, student_name: str, loop):
    market = sys.argv[2]
    subjects = [...] #Some large array
    batchsize = 5
    for i in range(0, len(subjects), batchsize):
        batch = subjects[i:i+batchsize]
        await asyncio.gather(*(self.work_async(student_name,
                                           sub['Code'],
                                           loop)
                       for sub in batch))

1

これを見てください。

http://docs.python.org/library/queue.html

これは正しい方法ではないかもしれませんが、私は次のようなことをします。

実際のコード。

from multiprocessing import Process, JoinableQueue as Queue 

class CustomWorker(Process):
    def __init__(self,workQueue, out1,out2,out3):
        Process.__init__(self)
        self.input=workQueue
        self.out1=out1
        self.out2=out2
        self.out3=out3
    def run(self):
            while True:
                try:
                    value = self.input.get()
                    #value modifier
                    temp1,temp2,temp3 = self.calc_stuff(value)
                    self.out1.put(temp1)
                    self.out2.put(temp2)
                    self.out3.put(temp3)
                    self.input.task_done()
                except Queue.Empty:
                    return
                   #Catch things better here
    def calc_stuff(self,param):
        out1 = param * 2
        out2 = param * 4
        out3 = param * 8
        return out1,out2,out3
def Main():
    inputQueue = Queue()
    for i in range(10):
        inputQueue.put(i)
    out1 = Queue()
    out2 = Queue()
    out3 = Queue()
    processes = []
    for x in range(2):
          p = CustomWorker(inputQueue,out1,out2,out3)
          p.daemon = True
          p.start()
          processes.append(p)
    inputQueue.join()
    while(not out1.empty()):
        print out1.get()
        print out2.get()
        print out3.get()
if __name__ == '__main__':
    Main()

お役に立てば幸いです。


1

これは、Pythonでマルチプロセッシングと並列/分散コンピューティングを実装するときに役立ちます。

techilaパッケージの使用に関するYouTubeチュートリアル

Techilaは、techilaパッケージを使用してPythonと直接統合する分散コンピューティングミドルウェアです。パッケージのpeach関数は、ループ構造の並列化に役立ちます。(次のコードスニペットはTechilaコミュニティフォーラムからのものです

techila.peach(funcname = 'theheavyalgorithm', # Function that will be called on the compute nodes/ Workers
    files = 'theheavyalgorithm.py', # Python-file that will be sourced on Workers
    jobs = jobcount # Number of Jobs in the Project
    )

1
このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。
SLバース-モニカを

2
@SLBarthフィードバックありがとうございます。小さなサンプルコードを回答に追加しました。
TEe

1

ありがとう@iuryxavier

from multiprocessing import Pool
from multiprocessing import cpu_count


def add_1(x):
    return x + 1

if __name__ == "__main__":
    pool = Pool(cpu_count())
    results = pool.map(add_1, range(10**12))
    pool.close()  # 'TERM'
    pool.join()   # 'KILL'

2
-1。これはコードのみの回答です。あなたが投稿したコードが何をしているか、そしておそらく彼らが追加情報を見つけることができる場所を読者に伝える説明を追加することをお勧めします。
starbeamrainbowlabs

-1

並列処理の非常に単純な例は

from multiprocessing import Process

output1 = list()
output2 = list()
output3 = list()

def yourfunction():
    for j in range(0, 10):
        # calc individual parameter value
        parameter = j * offset
        # call the calculation
        out1, out2, out3 = calc_stuff(parameter=parameter)

        # put results into correct output list
        output1.append(out1)
        output2.append(out2)
        output3.append(out3)

if __name__ == '__main__':
    p = Process(target=pa.yourfunction, args=('bob',))
    p.start()
    p.join()

3
ここではforループに並列処理はありません。ループ全体を実行するプロセスを生成しているだけです。これはOPが意図したものではありません。
1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.