간단한 파이썬 루프를 어떻게 병렬화합니까?


255

이것은 아마도 사소한 질문이지만 파이썬에서 다음 루프를 어떻게 병렬화합니까?

# 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)

파이썬에서 단일 스레드를 시작하는 방법을 알고 있지만 결과를 "수집"하는 방법을 모르겠습니다.

이 경우 가장 쉬운 방법은 여러 프로세스도 좋습니다. 현재 Linux를 사용하고 있지만 코드는 Windows 및 Mac에서 잘 실행되어야합니다.

이 코드를 병렬화하는 가장 쉬운 방법은 무엇입니까?

답변:


192

CPython에서 다중 스레드를 사용하면 GIL (Global Interpreter Lock)으로 인해 순수 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

@ user8188120 tqdm에 대해 들어 본 적이 없으므로 죄송합니다. 도와 드릴 수 없습니다.
Sven Marnach

tqdm 로딩 바에 대해서는이 질문을 참조하십시오 : stackoverflow.com/questions/41920124/…
Johannes

66

간단한 for 루프를 병렬화하기 위해 joblib 은 다중 처리의 원시 사용에 많은 가치를 제공합니다. 짧은 구문뿐만 아니라 매우 빠른 경우 (오버 헤드를 제거하기 위해) 자식 프로세스의 트레이스 백을 캡처 할 때 투명한 반복 묶음과 같은 것들이 오류보고를 더 잘 수행합니다.

면책 조항 : 나는 joblib의 최초 저자입니다.


1
jupyter로 joblib을 시도했지만 작동하지 않습니다. 병렬 지연 호출 후 페이지 작동이 중지되었습니다.
Jie

1
안녕하세요, joblib ( stackoverflow.com/questions/52166572/… )를 사용하는 데 문제 가 있습니다. 원인이 무엇인지 힌트가 있습니까? 매우 감사합니다.
Ting Sun

사진을주고 싶은 것 같습니다! 예를 들어 range (10)의 i에 대해 이중 루프와 함께 사용할 수 있습니까? 범위 (20)의 j에 대해
CutePoison

51

이 코드를 병렬화하는 가장 쉬운 방법은 무엇입니까?

정말 같은 나는 concurrent.futures이것에 대한, Python3에서 사용할 수있는 버전 3.2 이후 - 2.6로 백 포트를 통해 및 2.7 PyPi 합니다.

스레드 또는 프로세스를 사용하고 정확히 동일한 인터페이스를 사용할 수 있습니다.

멀티 프로세싱

이것을 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는 포크 기능을 지원하지 않으므로 각 새 프로세스를 시작하는 데 시간이 걸리기 때문에 멀티 스레딩이 일반적으로, 특히 Windows에서 멀티 프로세싱보다 빠를 것이라고 생각합니다. Linux 또는 Mac에서는 아마도 더 가까이있을 것입니다.

여러 프로세스 내에 여러 스레드를 중첩 할 수 있지만 여러 스레드를 사용하여 여러 프로세스를 분리하지 않는 것이 좋습니다.


ThreadPoolExecutor는 GIL에 의해 부과 된 제한을 우회합니까? 또한 executor가 완료되기를 기다리기 위해 join ()을 할 필요가 없거나 컨텍스트 관리자 내부에서 암시 적으로 처리됩니다.
PirateApp

1
아니, 아니, "암시 적으로 다루는"네
아론 홀

어떤 이유로 문제를 확장 할 때 멀티 스레딩은 매우 빠르지 만 멀티 프로세싱은 많은 중단 된 프로세스를 생성합니다 (macOS). 왜 그런지 알 수 있습니까? 이 프로세스에는 중첩 루프와 수학이 포함되어 있습니다.
komodovaran_

@komodovaran_ 프로세스는 각각 하나의 전체 파이썬 프로세스이며 스레드는 프로세스, 바이트 코드 및 기타 모든 스레드와 메모리에있는 모든 것을 공유하는 자체 스택이있는 실행 스레드입니다. ?
Aaron Hall

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)

위의 내용은 내 컴퓨터에서 아름답게 작동합니다 (우분투, 패키지 joblib는 사전 설치되었지만 다음을 통해 설치할 수 있습니다) pip install joblib ).

https://blog.dominodatalab.com/simple-parallelization/ 에서 가져온


3
코드를 시도했지만 시스템 에서이 코드의 순차 버전은 약 0.5 분이 걸리고 위의 병렬 버전은 4 분이 걸립니다. 왜 그래?
shaifali Gupta

3
답변 주셔서 감사합니다! 나는 이것이 2019
Heikki Pulkkinen

2
멀티 프로세싱은 Python 3.x에 유효하지 않으므로 작동하지 않습니다.
EngrStudent

2
@EngrStudent "유효하지 않음"이 무슨 뜻인지 잘 모르겠습니다. Python 3.6.x에서 작동합니다.
tyrex 2016 년

@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 () 호출 만 변경하면됩니다. 관련 문서는 여기 에서 찾을 수 있습니다 .

Ray 개발을 돕고 있습니다.


1
ray를 고려하는 사람은 Windows를 기본적으로 지원하지 않는다는 것을 알고있을 수 있습니다. WSL (Linux 용 Windows 서브 시스템)을 사용하여 Windows에서 작동하도록하는 일부 해킹이 가능하지만 Windows를 사용하려는 경우 기본적으로 사용하기는 어렵습니다.
OscarVanL

9

가장 쉬운 방법입니다!

asyncio 를 사용할 수 있습니다 . (문서는 여기 에서 찾을 수 있습니다 ). 고성능 네트워크 및 웹 서버, 데이터베이스 연결 라이브러리, 분산 작업 대기열 등을 제공하는 여러 Python 비동기 프레임 워크의 기초로 사용됩니다. 또한 모든 종류의 문제를 수용 할 수있는 고급 및 저수준 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
jakub-olczyk

죄송합니다! 내 실수. 수정했습니다!
USER5

6

왜 하나의 전역 목록을 보호하기 위해 스레드와 하나의 뮤텍스를 사용하지 않습니까?

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
나는 이것이 매우 오래된 대답이라는 것을 알고 있으므로 어디에서나 임의의 downvote를 얻는 것은 번거로운 일입니다. 스레드가 아무것도 병렬화하지 않기 때문에 다운 다운되었습니다. 파이썬의 스레드는 전역 인터프리터 잠금으로 인해 인터프리터에서 한 번에 하나의 스레드에만 바인딩되므로 동시 프로그래밍 을 지원 하지만 OP가 요청하는 병렬 처리 는 지원 하지 않습니다 .
skrrgwasme

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

이것은 파이썬에서 멀티 프로세싱 및 병렬 / 분산 컴퓨팅을 구현할 때 유용 할 수 있습니다.

techila 패키지 사용에 대한 YouTube 자습서

Techila는 분산 컴퓨팅 미들웨어로 techila 패키지를 사용하여 Python과 직접 통합됩니다. 패키지의 복숭아 함수는 루프 구조를 병렬화하는 데 유용 할 수 있습니다. (다음 코드 스 니펫은 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가 의도 한 것이 아닙니다.
facuq
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.