Parallelizing for-loop ใน Python


35

มีเครื่องมือใดใน Python ที่เหมือนกับ parlab ของ Matlab หรือไม่? ฉันพบกระทู้นี้แต่อายุสี่ขวบ ฉันคิดว่าบางทีคนที่นี่อาจมีประสบการณ์มากกว่านี้

นี่คือตัวอย่างของประเภทของสิ่งที่ฉันต้องการขนาน:

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

ที่my_functionจะใช้เวลาndarrayที่มีขนาด(1,3)และผลตอบแทนเกลา

อย่างน้อยที่สุดฉันต้องการใช้หลายคอร์พร้อมกัน --- เหมือน parfor กล่าวอีกนัยหนึ่งคือสมมติว่าระบบหน่วยความจำที่ใช้ร่วมกันมี 8 ถึง 16 คอร์


ผลลัพธ์จำนวนมากบน Google สิ่งเหล่านี้ดูเหมือนง่ายมาก: blog.dominodatalab.com/simple-parallelization quora.com/What-is-the-Python-equivalent-of-MATLABs-parfor
Doug Lipinski

ขอบคุณ @ doug-lipinski ตัวอย่างเหล่านั้นเช่นเดียวกับคนอื่น ๆ ที่ฉันพบในขณะใช้ Google มีการคำนวณเล็กน้อยตามดัชนีการวนซ้ำ และพวกเขามักอ้างสิทธิ์ในรหัสว่า "ง่ายอย่างเหลือเชื่อ" ตัวอย่างของฉันกำหนดอาร์เรย์ (จัดสรรหน่วยความจำ) นอก for-loop ฉันโอเคทำอย่างอื่น; นั่นเป็นเพียงวิธีที่ฉันทำใน Matlab ส่วนที่ยุ่งยากที่ดูเหมือนจะทำให้ตัวอย่างเหล่านั้นได้รับส่วนหนึ่งของอาร์เรย์ที่กำหนดให้กับฟังก์ชันภายในลูป
พอลจีคอนสแตนติน

คำตอบ:


19

Joblibทำในสิ่งที่คุณต้องการ รูปแบบการใช้งานพื้นฐานคือ:

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

โดยที่arg_instancesรายการของค่าที่myfunคำนวณในแบบคู่ขนาน ข้อ จำกัด หลักคือmyfunต้องเป็นฟังก์ชันระดับบนสุด backendพารามิเตอร์สามารถเป็นได้ทั้งหรือ"threading""multiprocessing"

คุณสามารถส่งพารามิเตอร์ทั่วไปเพิ่มเติมไปยังฟังก์ชันแบบขนาน เนื้อความของmyfunยังสามารถอ้างถึงตัวแปรโกลบอลที่เริ่มต้นได้ซึ่งเป็นค่าที่จะมีให้กับลูก

Args และผลลัพธ์สามารถเป็นอะไรก็ได้มาก ๆ ด้วยแบ็คเอนด์ที่เธรด แต่ผลลัพธ์นั้นต้องต่อเนื่องกันได้กับแบ็คเอนด์แบบ


Daskยังมีฟังก์ชั่นที่คล้ายกัน มันอาจจะดีกว่าถ้าคุณทำงานกับข้อมูลหลักหรือคุณพยายามที่จะทำการคำนวณที่ซับซ้อนมากขึ้นควบคู่กัน


ฉันเห็นค่าศูนย์เพิ่มเพื่อใช้แบตเตอรี่รวมถึงการประมวลผลหลาย ฉันจะเดิมพันว่า joblib ใช้งานภายใต้ประทุน
Xavier Combelle

1
จะต้องมีการกล่าวถึงว่า joblib ไม่ใช่เวทมนต์threadingแบ็กเอนด์ทนทุกข์ทรมานจากคอขวด GILและmultiprocessingแบ็กเอนด์นำค่าใช้จ่ายที่มีขนาดใหญ่เนื่องจากการเรียงลำดับของพารามิเตอร์ทั้งหมดและค่าส่งคืน ดูคำตอบนี้สำหรับรายละเอียดระดับต่ำของการประมวลผลแบบขนานใน Python
Jakub Klinkovský

ฉันไม่พบการผสมผสานของความซับซ้อนของฟังก์ชันและจำนวนการวนซ้ำที่ joblib จะเร็วกว่า for-loop สำหรับฉันมันมีความเร็วเท่ากันถ้า n_jobs = 1 และช้ากว่ามากในทุกกรณี
Aleksejs Fomins

@AleksejsFomins การทำงานแบบขนานตามเธรดจะไม่ช่วยให้โค้ดที่ไม่ปล่อย GIL แต่มีจำนวนมากที่ต้องทำโดยเฉพาะวิทยาศาสตร์ข้อมูลหรือไลบรารีที่เป็นตัวเลข มิฉะนั้นคุณจำเป็นต้องมีการประมวลผลร่วมกัน Jobli รองรับทั้งสองอย่าง โมดูลหลายตัวประมวลผลในขณะนี้ยังมีขนานmapที่คุณสามารถใช้โดยตรง นอกจากนี้หากคุณใช้ mkl ที่คอมไพล์แล้วก็จะทำให้การดำเนินการ vectorized เป็นแบบขนานโดยอัตโนมัติโดยที่คุณไม่ต้องทำอะไรเลย จำนวน numpy ใน Ananconda คือ mkl เปิดใช้งานโดยค่าเริ่มต้น ไม่มีวิธีแก้ปัญหาสากลว่า Joblib มีความยุ่งยากน้อยมากและมีจำนวนผู้เข้าร่วมน้อยกว่าในปี 2015
Daniel Mahler

ขอบคุณสำหรับคำแนะนำ. ฉันจำได้ว่าพยายามประมวลผลหลายตัวก่อนหน้าและแม้กระทั่งเขียนบทความสองสามโพสต์เพราะมันไม่ได้ขยายตามที่ฉันคาดไว้ บางทีฉันควรจะให้มันดูอีก
Aleksejs Fomins

9

สิ่งที่คุณกำลังมองหาคือNumbaซึ่งสามารถทำให้คู่ขนานวนซ้ำได้โดยอัตโนมัติ จากเอกสารของพวกเขา

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

หากไม่มีการคาดเดาสิ่งที่พิเศษเกี่ยวกับmy_functionการเลือกmultiprocessing.Pool().map()จะเป็นการเดาที่ดีสำหรับการวนซ้ำแบบลูปง่าย ๆ joblib, dask, mpiการคำนวณหรือnumbaเช่นที่นำเสนอในคำตอบลักษณะอื่น ๆ ที่ไม่นำประโยชน์ใด ๆ สำหรับกรณีการใช้งานดังกล่าวและเพิ่มการอ้างอิงไร้ประโยชน์ (ที่จะสรุปพวกเขาจะ overkill) การใช้เธรดตามที่เสนอในคำตอบอื่นนั้นไม่น่าจะเป็นทางออกที่ดีเพราะคุณต้องสนิทสนมกับปฏิสัมพันธ์ของ GIL ของรหัสของคุณหรือรหัสของคุณควรทำอินพุต / เอาท์พุตเป็นหลัก

ที่กล่าวว่าnumbaอาจเป็นความคิดที่ดีในการเร่งความเร็วของรหัสหลามบริสุทธิ์ตามลำดับ แต่ฉันรู้สึกว่ามันไม่ได้อยู่ในขอบเขตของคำถาม

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

อย่างไรก็ตามมีข้อควรระวังบางประการ (แต่ไม่ควรส่งผลกระทบต่อแอปพลิเคชันส่วนใหญ่):

  • ใต้ windows ไม่มีการรองรับ fork ดังนั้นจึงมีการเปิดตัวล่ามพร้อมโมดูลหลักเมื่อเริ่มต้นของเด็กแต่ละคนดังนั้นจึงอาจมีโอเวอร์เฮด (โฆษณาเป็นเหตุผลสำหรับ if __name__ == "__main__"
  • ข้อโต้แย้งและผลของการ my_function จะถูกดองและ unpickled ก็อาจจะมีค่าใช้จ่ายขนาดใหญ่เกินไปดูคำตอบนี้ได้ลดhttps://stackoverflow.com/a/37072511/128629 นอกจากนี้ยังทำให้วัตถุที่ไม่สามารถหยิบได้ไม่สามารถใช้งานได้
  • my_functionไม่ควรขึ้นอยู่กับสถานะที่แชร์เช่นการสื่อสารกับตัวแปรโกลบอลเนื่องจากสถานะไม่ได้ถูกแชร์ระหว่างกระบวนการ ฟังก์ชั่นบริสุทธิ์ (ฟังก์ชั่นในความรู้สึกทางคณิตศาสตร์) เป็นตัวอย่างของฟังก์ชั่นที่ไม่แบ่งปันรัฐ

6

ความประทับใจใน parfor ของฉันคือ MATLAB กำลังห่อหุ้มรายละเอียดการใช้งานดังนั้นจึงอาจใช้หน่วยความจำแบบคู่ขนานร่วมกัน (ซึ่งเป็นสิ่งที่คุณต้องการ) และหน่วยความจำแบบคู่ขนานแบบกระจาย (ถ้าคุณใช้เซิร์ฟเวอร์การคำนวณแบบกระจาย MATLAB )

หากคุณต้องการหน่วยความจำแบบแบ่งหน่วยความจำแบบแบ่งใช้และคุณกำลังดำเนินการงานแบบลูปขนานบางอย่างแพคเกจไลบรารีมัลติโพรเซสซิงอาจเป็นสิ่งที่คุณต้องการอาจมี front-end ที่ดีเช่นjoblibดังที่ระบุไว้ใน ไลบรารีมาตรฐานจะไม่หายไปและได้รับการดูแลรักษาดังนั้นจึงมีความเสี่ยงต่ำ

มีตัวเลือกอื่น ๆ ที่ออกมีมากเกินไปเช่นขนานงูหลามและIPython ความสามารถในแบบคู่ขนาน แวบเดียวอย่างรวดเร็วที่ Parallel Python ทำให้ฉันคิดว่ามันใกล้เคียงกับจิตวิญญาณของ parfor ซึ่งห้องสมุดจะสรุปรายละเอียดของคดีที่ถูกแจกจ่าย แต่ค่าใช้จ่ายในการทำเช่นนั้นคือคุณต้องนำระบบนิเวศมาใช้ ค่าใช้จ่ายในการใช้ IPython นั้นใกล้เคียงกัน คุณต้องยอมรับวิธีการทำสิ่งต่างๆของ IPython ซึ่งอาจจะคุ้มค่าหรือไม่คุ้มกับคุณ

ถ้าคุณดูแลเกี่ยวกับหน่วยความจำแบบกระจายผมขอแนะนำให้mpi4py Lisandro Dalcin ทำได้ดีมากและใช้ mpi4py ในการห่อหุ้ม PETSc Python ดังนั้นฉันจึงไม่คิดว่ามันจะหายไปในไม่ช้า มันเป็นอินเทอร์เฟซระดับต่ำ (เอ้อ) เพื่อขนานกันมากกว่า parfor แต่มีแนวโน้มที่จะอยู่ได้ระยะหนึ่ง


ขอบคุณ @Geoff คุณมีประสบการณ์ทำงานกับห้องสมุดเหล่านี้หรือไม่? บางทีฉันอาจลองใช้ mpi4py กับเครื่องหน่วยความจำร่วม / โปรเซสเซอร์มัลติคอร์
พอลจีคอนสแตนติน

@PaulGConstantine ฉันใช้งาน mpi4py เรียบร้อยแล้ว มันค่อนข้างเจ็บปวดถ้าคุณคุ้นเคยกับ MPI ฉันไม่ได้ใช้การประมวลผลหลายตัว แต่ฉันแนะนำให้เพื่อนร่วมงานที่บอกว่าใช้งานได้ดีสำหรับพวกเขา ฉันใช้ IPython ด้วย แต่ไม่ใช่คุณสมบัติแบบคู่ขนานดังนั้นฉันจึงไม่สามารถพูดได้ว่ามันใช้งานได้ดีเพียงใด
Geoff Oxberry

1
Aron มีบทช่วยสอนที่ดีที่เขาเตรียมไว้สำหรับหลักสูตร PyHPC ที่ Supercomputing: github.com/pyHPC/pyhpc-tutorial
Matt Knepley

4

ก่อนที่จะมองหาเครื่องมือ "กล่องดำ" ที่สามารถใช้ในการดำเนินการในฟังก์ชั่นหลาม "ทั่วไป" ฉันขอแนะนำให้วิเคราะห์ว่าmy_function()สามารถจัดเรียงด้วยมือได้อย่างไร

ก่อนอื่นให้เปรียบเทียบเวลาดำเนินการของโอเวอร์คmy_function(v)ล็อกforแบบหลามหลาม: [C] forลูปไพ ธ อนค่อนข้างช้าดังนั้นเวลาที่ใช้ไปmy_function()อาจน้อยมาก

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

ตรวจสอบครั้งที่สองว่ามีการใช้เว็กเตอร์อย่างง่ายmy_function(v)ซึ่งไม่จำเป็นต้องใช้ลูปหรือไม่:F[:] = my_vector_function(X)

(จุดแรกที่สองเหล่านี้ค่อนข้างน่ารำคาญยกโทษให้ฉันถ้าฉันพูดถึงพวกเขาที่นี่เพียงเพื่อความสมบูรณ์)

ที่สามและจุดสำคัญที่สุดอย่างน้อยสำหรับการใช้งาน CPython คือการตรวจสอบว่าmy_functionใช้เวลาส่วนใหญ่ของมันเวลาที่อยู่ภายในหรือนอกล็อคล่ามทั่วโลกหรือGIL หากใช้เวลานอก GIL ควรใช้threadingโมดูลไลบรารีมาตรฐาน ( นี่คือตัวอย่าง) BTW ใคร ๆ ก็คิดว่าการเขียนmy_function()เป็นส่วนเสริม C เพื่อปล่อย GIL

ในที่สุดหากmy_function()ไม่ปล่อย GIL เราสามารถใช้multiprocessingโมดูลได้

อ้างอิง: เอกสารหลามในการดำเนินการพร้อมกันและnumpy / SciPy บทนำในการประมวลผลแบบขนาน


2

คุณสามารถลองจูเลีย มันค่อนข้างใกล้กับ Python และมีการสร้าง MATLAB มากมาย การแปลที่นี่คือ:

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

สิ่งนี้ทำให้ตัวเลขสุ่มในแบบคู่ขนานด้วยและเพียงแค่เชื่อมผลลัพธ์ในท้ายที่สุดระหว่างการลด ที่ใช้การประมวลผลหลายตัว (ดังนั้นคุณต้องทำaddprocs(N)เพื่อเพิ่มกระบวนการก่อนที่จะใช้และยังสามารถใช้ได้กับหลายโหนดบน HPC ดังที่แสดงในโพสต์บล็อกนี้ )

คุณสามารถใช้pmapแทน:

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

ถ้าคุณต้องการเธรดขนานคุณสามารถใช้Threads.@threads(แต่ต้องแน่ใจว่าคุณทำให้อัลกอริธึมเป็นเธรดที่ปลอดภัย) ก่อนเปิด Julia ให้ตั้งค่าตัวแปรสภาพแวดล้อม JULIA_NUM_THREADS จากนั้นก็คือ:

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

ที่นี่ฉันสร้างอาเรย์แยกต่างหากสำหรับแต่ละเธรดดังนั้นวิธีที่จะไม่ขัดแย้งกันเมื่อเพิ่มไปยังอาเรย์จากนั้นก็ต่ออาเรย์เข้าด้วยกัน การทำเกลียวค่อนข้างใหม่ดังนั้นตอนนี้มีเพียงการใช้เธรดโดยตรง แต่ฉันแน่ใจว่าการลดเธรดและแผนที่จะถูกเพิ่มเช่นเดียวกับการประมวลผลหลายอย่าง


0

ฉันแนะนำให้ใช้ฟังก์ชั่น Parallel และ Delay ไลบรารี่ของ joblib ใช้โมดูล "tempfile" เพื่อสร้าง temp memory memory สำหรับอาร์เรย์ขนาดใหญ่ตัวอย่างและการใช้งานสามารถพบได้ที่นี่https://pythonhosted.org/joblib/parallel.html

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.