Làm thế nào để tôi song song hóa một vòng lặp Python đơn giản?


255

Đây có lẽ là một câu hỏi tầm thường, nhưng làm thế nào để tôi song song hóa vòng lặp sau trong 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)

Tôi biết cách bắt đầu các chuỗi đơn trong Python nhưng tôi không biết cách "thu thập" kết quả.

Nhiều quy trình cũng sẽ ổn - bất cứ điều gì là dễ dàng nhất trong trường hợp này. Tôi hiện đang sử dụng Linux nhưng mã cũng sẽ chạy trên Windows và Mac.

Cách dễ nhất để song song mã này là gì?

Câu trả lời:


193

Sử dụng nhiều luồng trên CPython sẽ không cung cấp cho bạn hiệu suất tốt hơn cho mã Python thuần do khóa trình thông dịch toàn cầu (GIL). Tôi đề nghị sử dụng multiprocessingmô-đun thay thế:

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

Lưu ý rằng điều này sẽ không hoạt động trong trình thông dịch tương tác.

Để tránh FUD thông thường xung quanh GIL: Dù sao đi nữa, sẽ không có bất kỳ lợi thế nào khi sử dụng các luồng cho ví dụ này. Bạn muốn sử dụng các quy trình ở đây, không phải các chủ đề, bởi vì chúng tránh được cả đống vấn đề.


46
Vì đây là câu trả lời được chọn, nên có thể có một ví dụ toàn diện hơn không? Các đối số của là calc_stuffgì?
Eduardo Pignatelli

2
@EduardoPignatelli Vui lòng chỉ đọc tài liệu của multiprocessingmô-đun để biết ví dụ toàn diện hơn. Pool.map()về cơ bản hoạt động như thế map(), nhưng song song.
Sven Marnach

3
Có cách nào để thêm một thanh tải tqdm vào cấu trúc mã này không? Tôi đã sử dụng tqdm (pool.imap (calc_ ware, phạm vi (0, 10 * offset, offset))) nhưng tôi không có được một đồ họa thanh tải đầy đủ.
dùng8188120

@ user8188120 Tôi chưa bao giờ nghe nói về tqdm trước đây, xin lỗi, tôi không thể giúp với điều đó.
Sven Marnach

Đối với một thanh tải tqdm thấy câu hỏi này: stackoverflow.com/questions/41920124/...
Johannes

67

Để song song hóa một vòng lặp đơn giản, joblib mang lại rất nhiều giá trị cho việc sử dụng thô của đa xử lý. Không chỉ cú pháp ngắn, mà cả những thứ như lặp đi lặp lại trong suốt khi chúng rất nhanh (để loại bỏ chi phí) hoặc nắm bắt được quá trình truy nguyên của quá trình con, để báo cáo lỗi tốt hơn.

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả gốc của joblib.


1
Tôi đã thử joblib với jupyter, nó không hoạt động. Sau cuộc gọi bị trì hoãn song song, trang ngừng hoạt động.
Jie

1
Xin chào, tôi gặp sự cố khi sử dụng joblib ( stackoverflow.com/questions/52166572/ ,), bạn có bất kỳ manh mối nào có thể là nguyên nhân không? Cảm ơn rất nhiều.
Ting Sun

Có vẻ như một cái gì đó tôi muốn đưa ra một shot! Có thể sử dụng nó với một vòng lặp kép, ví dụ như i trong phạm vi (10): cho j trong phạm vi (20)
CutePoison

51

Cách dễ nhất để song song mã này là gì?

Tôi thực sự thích concurrent.futuresđiều này, có sẵn trong Python3 kể từ phiên bản 3.2 - và thông qua backport đến 2.6 và 2.7 trên PyPi .

Bạn có thể sử dụng các chủ đề hoặc quy trình và sử dụng cùng một giao diện.

Đa xử lý

Đặt cái này trong một tập tin - 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()

Và đây là đầu ra:

$ 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]

Đa luồng

Bây giờ thay đổi ProcessPoolExecutorthành ThreadPoolExecutorvà chạy lại mô-đun:

$ 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]

Bây giờ bạn đã thực hiện cả đa luồng và đa xử lý!

Lưu ý về hiệu suất và sử dụng cả hai cùng nhau.

Lấy mẫu là quá nhỏ để so sánh kết quả.

Tuy nhiên, tôi nghi ngờ rằng đa luồng sẽ nhanh hơn đa xử lý nói chung, đặc biệt là trên Windows, vì Windows không hỗ trợ giả mạo nên mỗi quy trình mới phải mất thời gian để khởi chạy. Trên Linux hoặc Mac, có lẽ họ sẽ gần gũi hơn.

Bạn có thể lồng nhiều luồng trong nhiều tiến trình, nhưng không nên sử dụng nhiều luồng để tách ra nhiều tiến trình.


ThreadPoolExecutor có bỏ qua các giới hạn do GIL áp đặt không? bạn cũng không cần phải tham gia () để chờ người thực thi kết thúc hoặc việc này được xử lý ngầm trong trình quản lý bối cảnh
PirateApp

1
Không và không, có để "xử lý ngầm"
Aaron Hall

Vì một số lý do, khi nhân rộng vấn đề, đa luồng cực kỳ nhanh, nhưng đa xử lý sẽ sinh ra một loạt các quy trình bị kẹt (trong macOS). Bất cứ ý tưởng tại sao điều đó có thể được? Quá trình chỉ chứa các vòng lặp lồng nhau và toán học, không có gì kỳ lạ.
komodovaran_

@komodovaran_ Một tiến trình là một tiến trình Python đầy đủ, mỗi tiến trình, trong khi một luồng chỉ là một luồng thực thi với ngăn xếp riêng của nó chia sẻ tiến trình, mã byte của nó và mọi thứ khác có trong bộ nhớ với tất cả các luồng khác - điều đó có giúp ?
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)

Các công cụ trên hoạt động rất tốt trên máy của tôi (Ubuntu, gói joblib đã được cài đặt sẵn, nhưng có thể được cài đặt qua pip install joblib ).

Lấy từ https://blog.dominodirthab.com/simple- Championsization /


3
Tôi đã thử mã của bạn nhưng trên hệ thống của tôi, phiên bản tuần tự của mã này mất khoảng nửa phút và phiên bản song song ở trên mất 4 phút. Tại sao vậy?
shaifali Gupta

3
Cảm ơn câu trả lời của bạn! Tôi nghĩ rằng đây là cách thanh lịch nhất để làm điều này vào năm 2019.
Heikki Pulkkinen

2
đa xử lý không hợp lệ đối với Python 3.x, vì vậy điều này không hiệu quả với tôi.
EngrStudent

2
@EngrStudent Không chắc ý của bạn là "không hợp lệ". Nó hoạt động cho Python 3.6.x cho tôi.
tyrex

@tyrex cảm ơn vì đã chia sẻ! gói joblib này là tuyệt vời và ví dụ làm việc cho tôi. Mặc dù, trong một bối cảnh phức tạp hơn, tôi đã có một lỗi không may. github.com/joblib/joblib/issues/949
Nhà môi giới thực phẩm mở

13

Có một số lợi thế khi sử dụng Ray :

  • Bạn có thể song song trên nhiều máy cùng với nhiều lõi (có cùng mã).
  • Xử lý hiệu quả dữ liệu số thông qua bộ nhớ dùng chung (và tuần tự hóa không sao chép).
  • Thông lượng nhiệm vụ cao với lập kế hoạch phân phối.
  • Chịu lỗi.

Trong trường hợp của bạn, bạn có thể khởi động Ray và xác định chức năng từ xa

import ray

ray.init()

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

và sau đó gọi nó song song

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)

Để chạy cùng một ví dụ trên một cụm, dòng duy nhất sẽ thay đổi sẽ là lệnh gọi ray.init (). Các tài liệu liên quan có thể được tìm thấy ở đây .

Lưu ý rằng tôi đang giúp phát triển Ray.


1
Đối với bất kỳ ai xem xét tia, có thể có liên quan để biết nó không hỗ trợ Windows. Một số hack để làm cho nó hoạt động trong Windows bằng WSL (Hệ thống con Windows cho Linux) là có thể, mặc dù nó hầu như không vượt trội nếu bạn muốn sử dụng Windows.
OscarVanL

9

Đây là cách dễ nhất để làm điều đó!

Bạn có thể sử dụng asyncio . (Tài liệu có thể được tìm thấy ở đây ). Nó được sử dụng làm nền tảng cho nhiều khung không đồng bộ Python cung cấp mạng và máy chủ web hiệu suất cao, thư viện kết nối cơ sở dữ liệu, hàng đợi tác vụ phân tán, v.v. .

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

Bây giờ chức năng này sẽ được chạy song song bất cứ khi nào được gọi mà không đưa chương trình chính vào trạng thái chờ. Bạn có thể sử dụng nó để song song cho vòng lặp là tốt. Khi được gọi cho một vòng lặp for, mặc dù vòng lặp là tuần tự nhưng mỗi lần lặp lại chạy song song với chương trình chính ngay khi trình thông dịch đến đó. Ví dụ:

@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')

Điều này tạo ra đầu ra sau:

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

Tôi nghĩ rằng có một lỗi đánh máy wrapped()và nó nên được **kwargsthay vì*kwargs
jakub-olchot

Giáo sư! Lỗi của tôi. Đã sửa!
Người dùng5

6

Tại sao bạn không sử dụng các chủ đề và một mutex để bảo vệ một danh sách toàn cầu?

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

Hãy ghi nhớ, bạn sẽ nhanh như sợi chỉ chậm nhất của bạn


2
Tôi biết đây là một câu trả lời rất cũ, vì vậy đây là một mánh khóe để có được một downvote ngẫu nhiên ra khỏi hư không. Tôi chỉ hạ cấp bởi vì các chủ đề sẽ không song song bất cứ điều gì. Các luồng trong Python bị ràng buộc chỉ có một luồng thực thi trên trình thông dịch tại một thời điểm vì khóa trình thông dịch toàn cầu, vì vậy chúng hỗ trợ lập trình đồng thời, nhưng không song song như OP đang yêu cầu.
skrrgwasme

3
@skrrgwasme Tôi biết bạn biết điều này, nhưng khi bạn sử dụng từ "họ sẽ không song song hóa bất cứ điều gì", điều đó có thể khiến độc giả hiểu lầm. Nếu các hoạt động mất nhiều thời gian vì chúng bị ràng buộc IO hoặc ngủ trong khi chúng chờ đợi một sự kiện, thì trình thông dịch sẽ được giải phóng để chạy các luồng khác, vì vậy điều này sẽ dẫn đến việc mọi người hy vọng sẽ tăng tốc độ trong những trường hợp đó. Chỉ các luồng bị ràng buộc CPU thực sự bị ảnh hưởng bởi những gì skrrgwasme nói.
Jonathan Hartley

5

Tôi thấy joblibrất hữu ích với tôi. Vui lòng xem ví dụ sau:

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: sử dụng tất cả các lõi có sẵn


14
Bạn biết đấy, tốt hơn là kiểm tra các câu trả lời đã có trước khi đăng bài của riêng bạn. Câu trả lời này cũng đề xuất sử dụng joblib.
Tweetsash

2

Giả sử chúng ta có chức năng không đồng bộ

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

Điều đó cần phải được chạy trên một mảng lớn. Một số thuộc tính đang được truyền cho chương trình và một số thuộc tính được sử dụng từ thuộc tính của phần tử từ điển trong mảng.

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

Có một cái nhìn tại đây;

http://docs.python.org/l Library /queue.html

Đây có thể không phải là cách đúng đắn để làm điều đó, nhưng tôi sẽ làm một cái gì đó như;

Mã thực tế;

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

Mong rằng sẽ giúp.


1

Điều này có thể hữu ích khi triển khai tính toán đa xử lý và song song / phân tán trong Python.

Hướng dẫn YouTube về cách sử dụng gói techila

Techila là một phần mềm trung gian điện toán phân tán, tích hợp trực tiếp với Python bằng gói techila. Hàm đào trong gói có thể hữu ích trong việc song song hóa các cấu trúc vòng lặp. (Đoạn mã sau là từ Diễn đàn Cộng đồng 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
Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi.
SL Barth - Phục hồi Monica

2
@SLBarth cảm ơn bạn đã phản hồi. Tôi đã thêm một mã mẫu nhỏ vào câu trả lời.
TEe

1

cảm ơn @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. Đây là một câu trả lời chỉ có mã. Tôi khuyên bạn nên thêm một lời giải thích cho người đọc biết mã bạn đã đăng là gì và có lẽ họ có thể định vị thông tin bổ sung ở đâu.
starbeamrainbowlabs

-1

ví dụ rất đơn giản về xử lý song song là

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
Không có sự song song trong vòng lặp for ở đây, bạn chỉ đang tạo ra một quy trình chạy toàn bộ vòng lặp; Đây không phải là những gì OP dự định.
facuq
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.