Song song một vòng lặp for trong Python


35

Có công cụ nào trong Python giống như parfor của Matlab không? Tôi tìm thấy chủ đề này , nhưng nó bốn tuổi. Tôi nghĩ có lẽ ai đó ở đây có thể có nhiều kinh nghiệm gần đây.

Đây là một ví dụ về loại điều tôi muốn song song:

X = np.random.normal(size=(10, 3))
F = np.zeros((10, ))
for i in range(10):
    F[i] = my_function(X[i,:])

trong đó my_functioncó một ndarraykích thước (1,3)và trả về một vô hướng.

Ít nhất, tôi muốn sử dụng đồng thời nhiều lõi --- như parfor. Nói cách khác, giả sử một hệ thống bộ nhớ dùng chung có lõi 8 đến 16.


Rất nhiều kết quả trên google. Những điều này có vẻ khá đơn giản: blog.dominodirthab.com/simple- Championsization quora.com/What-is-the-Python-equivalent-of-MATLABs-parfor
Doug Lipinski

Cảm ơn, @ doug-lipinski. Những ví dụ đó, giống như những ví dụ khác mà tôi đã tìm thấy khi đang googling, có một số tính toán tầm thường dựa trên chỉ số lặp. Và họ luôn khẳng định mã là "cực kỳ dễ dàng". Ví dụ của tôi định nghĩa các mảng (phân bổ bộ nhớ) bên ngoài vòng lặp for. Tôi ổn khi làm theo cách khác; đó chỉ là cách tôi làm trong Matlab. Phần khó khăn mà dường như bỏ qua các ví dụ đó là lấy một phần của một mảng nhất định cho hàm bên trong vòng lặp.
Paul G. Constantine

Câu trả lời:


19

Joblib làm những gì bạn muốn. Mẫu sử dụng cơ bản là:

from joblib import Parallel, delayed

def myfun(arg):
     do_stuff
     return result

results = Parallel(n_jobs=-1, verbose=verbosity_level, backend="threading")(
             map(delayed(myfun), arg_instances))

trong đó arg_instancesdanh sách các giá trị myfunđược tính song song. Hạn chế chính là myfunphải là một hàm toplevel. Các backendtham số có thể là "threading"hay "multiprocessing".

Bạn có thể truyền các tham số phổ biến bổ sung cho hàm song song. Phần thân myfuncũng có thể đề cập đến các biến toàn cục khởi tạo, các giá trị sẽ có sẵn cho trẻ em.

Các đối số và kết quả có thể là khá nhiều bất cứ điều gì với phụ trợ luồng, nhưng kết quả cần phải được tuần tự hóa với phụ trợ đa xử lý.


Dask cũng cung cấp chức năng tương tự. Có thể tốt hơn nếu bạn đang làm việc với dữ liệu cốt lõi hoặc bạn đang cố gắng song song hóa các tính toán phức tạp hơn.


Tôi thấy giá trị gia tăng bằng 0 để sử dụng pin bao gồm cả đa xử lý. Tôi đặt cược rằng joblib đang sử dụng nó dưới mui xe.
Xavier Combelle

1
Phải nói rằng joblib không phải là phép thuật, threadingphụ trợ bị tắc nghẽn GILmultiprocessingphụ trợ mang lại chi phí lớn do việc tuần tự hóa tất cả các tham số và trả về giá trị. Xem câu trả lời này để biết chi tiết cấp thấp về xử lý song song trong Python.
Jakub Klinkovský

Tôi không thể tìm thấy sự kết hợp của độ phức tạp của hàm và số lần lặp mà joblib sẽ nhanh hơn vòng lặp for. Đối với tôi, nó có cùng tốc độ nếu n_jobs = 1, và chậm hơn rất nhiều trong tất cả các trường hợp khác
Mitchsejs Fomins

@AleksejsFomins Tính song song dựa trên chủ đề sẽ không giúp ích cho mã không phát hành GIL nhưng một số lượng đáng kể làm, đặc biệt là khoa học dữ liệu hoặc thư viện số. Nếu không, bạn cần xử lý đột biến, Jobli hỗ trợ cả hai. Mô-đun đa xử lý bây giờ cũng có song song mapmà bạn có thể sử dụng trực tiếp. Ngoài ra nếu bạn sử dụng mkl được biên dịch numpy, nó sẽ tự động song song hóa các hoạt động được vector hóa mà không cần bạn làm gì cả. Numpy trong Ananconda là mkl được bật theo mặc định. Không có giải pháp phổ quát mặc dù. Joblib rất ít ồn ào và có ít cảm xúc hơn trong năm 2015.
Daniel Mahler

Cảm ơn lời khuyên của bạn. Tôi nhớ đã thử xử lý đa xử lý trước đó và thậm chí viết một vài bài đăng, vì nó không mở rộng như tôi mong đợi. Có lẽ tôi nên cho nó một cái nhìn khác
Fseins của Mitchsejs

9

Thứ bạn đang tìm kiếm là Numba , có thể tự động song song một vòng lặp for. Từ tài liệu của họ

from numba import jit, prange

@jit
def parallel_sum(A):
    sum = 0.0
    for i in prange(A.shape[0]):
        sum += A[i]

    return sum

8

Không giả định một cái gì đó đặc biệt về my_functionviệc lựa chọn multiprocessing.Pool().map()là một dự đoán tốt cho việc song song các vòng lặp đơn giản như vậy. joblib, dask, mpiTính toán hay numbanhư đề xuất trong câu trả lời khác trông không mang lại bất kỳ lợi thế đối với trường hợp sử dụng đó và thêm phụ thuộc vô dụng (để tổng hợp chúng là quá mức cần thiết). Sử dụng luồng như đề xuất trong một câu trả lời khác dường như không phải là một giải pháp tốt, bởi vì bạn phải thân mật với tương tác GIL của mã của bạn hoặc mã của bạn nên thực hiện chủ yếu đầu vào / đầu ra.

Điều đó nói rằng numbacó thể là một ý tưởng tốt để tăng tốc mã python thuần tuần tự, nhưng tôi cảm thấy điều này nằm ngoài phạm vi của câu hỏi.

import multiprocessing
import numpy as np

if __name__ == "__main__":
   #the previous line is necessary under windows to not execute 
   # main module on each child under windows

   X = np.random.normal(size=(10, 3))
   F = np.zeros((10, ))

   pool = multiprocessing.Pool(processes=16)
   # if number of processes is not specified, it uses the number of core
   F[:] = pool.map(my_function, (X[i,:] for i in range(10)) )

Tuy nhiên, có một số cảnh báo (nhưng sẽ không ảnh hưởng đến hầu hết các ứng dụng):

  • bên dưới cửa sổ không có hỗ trợ ngã ba, do đó, một trình thông dịch với mô-đun chính được khởi chạy khi khởi động của mỗi đứa trẻ, vì vậy nó có thể có một chi phí hoạt động (quảng cáo đó là lý do cho if __name__ == "__main__"
  • Các đối số và kết quả của my_factor được chọn và giải nén, nó có thể là một chi phí quá lớn, hãy xem câu trả lời này để giảm bớt https://stackoverflow.com/a/37072511/128629 . Nó cũng làm cho các đối tượng không thể chọn không thể sử dụng
  • my_functionkhông nên phụ thuộc vào các trạng thái được chia sẻ như giao tiếp với các biến toàn cục vì các trạng thái không được chia sẻ giữa quá trình. các hàm thuần túy (các hàm trong các giác quan toán học) là ví dụ về các hàm không chia sẻ trạng thái

6

Ấn tượng của tôi về parfor là MATLAB đang gói gọn các chi tiết triển khai, do đó, nó có thể sử dụng cả song song bộ nhớ dùng chung (đó là những gì bạn muốn) và song song bộ nhớ phân tán (nếu bạn đang chạy máy chủ tính toán phân tán MATLAB ).

Nếu bạn muốn chia sẻ song song bộ nhớ và bạn đang thực hiện một số loại vòng lặp nhiệm vụ song song, gói thư viện tiêu chuẩn đa xử lý có thể là những gì bạn muốn, có thể với một giao diện đẹp, như joblib , như được đề cập trong bài đăng của Doug. Thư viện tiêu chuẩn sẽ không biến mất và nó được duy trì, vì vậy nó có rủi ro thấp.

Ngoài ra còn có các tùy chọn khác, như các khả năng song song của Parallel PythonIPython . Một cái nhìn thoáng qua về Parallel Python khiến tôi nghĩ rằng nó gần với tinh thần của parfor hơn, trong đó thư viện gói gọn các chi tiết cho trường hợp phân tán, nhưng chi phí để làm như vậy là bạn phải áp dụng hệ sinh thái của họ. Chi phí sử dụng IPython là tương tự; bạn phải áp dụng cách làm IPython, có thể có hoặc không có giá trị với bạn.

Nếu bạn quan tâm đến bộ nhớ phân tán, tôi khuyên dùng mpi4py . Lisandro Dalcin làm rất tốt và mpi4py được sử dụng trong các trình bao bọc Python của PETSc, vì vậy tôi không nghĩ rằng nó sẽ biến mất bất cứ lúc nào sớm. Giống như đa xử lý, đó là một giao diện thấp (er) -level để song song hơn so với parfor, nhưng một giao diện có thể tồn tại trong một thời gian.


Cảm ơn, @Geoff. Bạn có bất kỳ kinh nghiệm làm việc với các thư viện này? Có lẽ tôi sẽ thử sử dụng mpi4py trên bộ nhớ / bộ xử lý đa lõi dùng chung.
Paul G. Constantine

@PaulGConstantine Tôi đã sử dụng mpi4py thành công; Sẽ không đau, nếu bạn quen thuộc với Bộ KH & ĐT. Tôi chưa sử dụng đa xử lý, nhưng tôi đã giới thiệu nó cho các đồng nghiệp, những người nói rằng nó hoạt động tốt cho họ. Tôi cũng đã sử dụng IPython, nhưng không phải là các tính năng song song, vì vậy tôi không thể nói nó hoạt động tốt như thế nào.
Geoff Oxberry

1
Aron có một hướng dẫn mpi4py tuyệt vời mà anh ấy đã chuẩn bị cho khóa học PyHPC tại Supercomputing: github.com/pyHPC/pyhpc-tutorial
Matt Knepley 17/05/2015

4

Trước khi tìm kiếm một công cụ "hộp đen", có thể được sử dụng để thực thi các hàm python "chung" song song, tôi sẽ đề nghị phân tích làm thế nào my_function()có thể song song bằng tay.

Đầu tiên, so sánh thời gian thực hiện của chi phí vòng lặp my_function(v)python for: [C] Các forvòng lặp Python khá chậm, vì vậy thời gian sử dụng my_function()có thể không đáng kể.

>>> timeit.timeit('pass', number=1000000)
0.01692986488342285
>>> timeit.timeit('for i in range(10): pass', number=1000000)
0.47521495819091797
>>> timeit.timeit('for i in xrange(10): pass', number=1000000)
0.42337894439697266

Kiểm tra thứ hai nếu có một triển khai vectơ đơn giản my_function(v)không yêu cầu các vòng lặp:F[:] = my_vector_function(X)

(Hai điểm đầu tiên này khá tầm thường, hãy tha thứ cho tôi nếu tôi đề cập đến chúng ở đây chỉ vì sự hoàn chỉnh.)

Thứ ba và hầu hết các điểm quan trọng, ít nhất là cho triển khai CPython, là để kiểm tra xem my_functiondành phần lớn thời gian của nó bên trong hoặc bên ngoài các khóa phiên dịch toàn cầu , hoặc GIL . Nếu thời gian được sử dụng ngoài GIL, thì nên sử dụng threadingmô-đun thư viện tiêu chuẩn . ( Đây là một ví dụ). BTW, người ta có thể nghĩ về việc viết my_function()như một phần mở rộng C chỉ để phát hành GIL.

Cuối cùng, nếu my_function()không giải phóng GIL, người ta có thể sử dụng multiprocessingmô-đun .

Tài liệu tham khảo: Tài liệu Python về Thực thi đồng thờigiới thiệu numpy / scipy về xử lý song song .


2

Bạn có thể thử Julia. Nó khá gần với Python và có rất nhiều cấu trúc MATLAB. Bản dịch ở đây là:

F = @parallel (vcat) for i in 1:10
    my_function(randn(3))
end

Điều này làm cho các số ngẫu nhiên song song quá, và chỉ kết hợp các kết quả cuối cùng trong quá trình giảm. Điều đó sử dụng đa xử lý (vì vậy bạn cần phải làm addprocs(N)để thêm các quy trình trước khi sử dụng và điều này cũng hoạt động trên nhiều nút trên HPC như trong bài đăng trên blog này ).

Bạn cũng có thể sử dụng pmapthay thế:

F = pmap((i)->my_function(randn(3)),1:10)

Nếu bạn muốn xử lý song song luồng, bạn có thể sử dụng Threads.@threads(mặc dù hãy chắc chắn rằng bạn thực hiện thuật toán luồng an toàn). Trước khi mở Julia, hãy đặt biến môi trường JULIA_NUM_THREADS, sau đó là:

Ftmp = [Float64[] for i in Threads.nthreads()]
Threads.@threads for i in 1:10
    push!(Ftmp[Threads.threadid()],my_function(randn(3)))
end
F = vcat(Ftmp...)

Ở đây tôi tạo một mảng riêng cho mỗi luồng, để chúng không bị xung đột khi thêm vào mảng, sau đó chỉ ghép các mảng sau đó. Việc phân luồng là khá mới vì vậy hiện tại chỉ có việc sử dụng trực tiếp các luồng, nhưng tôi chắc chắn việc cắt giảm luồng và bản đồ sẽ được thêm vào giống như khi xử lý đa xử lý.


0

Tôi khuyên bạn nên sử dụng các chức năng Song song và trì hoãn của thư viện joblib sử dụng mô-đun "tempfile" để tạo bộ nhớ chia sẻ tạm thời cho các mảng lớn, có thể tìm thấy các ví dụ và cách sử dụng tại đây https://pythonhosted.org/joblib/abul.html

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.