Basit bir Python döngüsünü nasıl paralelleştirebilirim?


256

Bu muhtemelen önemsiz bir soru, ancak aşağıdaki döngüyü python'da nasıl paralelleştirebilirim?

# 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 tek konuları başlatmak nasıl biliyorum ama nasıl sonuçları "toplamak" bilmiyorum.

Birden çok işlem de iyi olur - bu durumda en kolayı ne olursa olsun. Şu anda Linux kullanıyorum ama kod Windows ve Mac üzerinde de çalışmalıdır.

Bu kodu paralelleştirmenin en kolay yolu nedir?

Yanıtlar:


192

CPython'da birden çok iş parçacığı kullanmak, genel yorumlayıcı kilidi (GIL) nedeniyle saf Python kodu için daha iyi performans sağlamaz. Bunun multiprocessingyerine modülü kullanmanızı öneririm :

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

Bunun etkileşimli yorumlayıcıda çalışmadığını unutmayın.

GIL etrafında normal FUD'den kaçınmak için: Yine de bu örnek için iplik kullanmanın herhangi bir avantajı olmazdı. Sen istemek onlar sorunların bir sürü önlemek için, burada dizileri değil yöntemlerinin kullanılması.


47
Seçilen cevap bu olduğundan, daha kapsamlı bir örneğe sahip olmak mümkün mü? Tartışmaları nelerdir calc_stuff?
Eduardo Pignatelli

2
@EduardoPignatelli multiprocessingDaha kapsamlı örnekler için lütfen modülün belgelerini okuyun . Pool.map()temelde map(), ama paralel olarak çalışır .
Sven Marnach

3
Bu kod yapısına bir tqdm yükleme çubuğuna eklemenin bir yolu var mı? Ben tqdm (pool.imap (calc_stuff, range (0, 10 * ofset, offset))) kullandım ama tam bir yükleme çubuğu grafiği almıyorum.
user8188120

@ user8188120 Daha önce hiç tqdm duymadım, çok üzgünüm, bu konuda yardım edemem.
Sven Marnach

Tqdm yükleme çubuğu için şu soruya bakın: stackoverflow.com/questions/41920124/…
Johannes

67

Basit bir döngüyü paralel hale getirmek için, joblib çok işlemciliğin ham kullanımına çok değer katıyor . Daha kısa bir sözdizimi değil, aynı zamanda çok daha hızlı olduklarında (ek yükü kaldırmak için) yinelemelerin şeffaf bir şekilde demetlenmesi veya alt işlemin izinin yakalanması, daha iyi hata raporlaması için gibi şeyler.

Feragatname: Ben joblib'in asıl yazarıyım.


1
Joblib'i jupyter ile denedim, çalışmıyor. Paralel gecikmeli aramadan sonra sayfa çalışmayı durdurdu.
Jie

1
Merhaba, joblib ( stackoverflow.com/questions/52166572/… ) ile ilgili bir sorunum var, sebebinin ne olabileceğine dair bir fikrin var mı? Çok teşekkürler.
Ting Sun

Denemek istediğim bir şey gibi görünüyor! Bir çift döngü ile kullanmak mümkün mü, örneğin (10) aralığında i için: (20) aralığında j için
CutePoison

51

Bu kodu paralelleştirmenin en kolay yolu nedir?

Bunu gerçekten seviyorum concurrent.futures, sürüm 3.2'den beri Python3'te mevcut - ve PyPi'de 2.6 ve 2.7'ye backport üzerinden .

İş parçacıklarını veya işlemleri kullanabilir ve aynı arabirimi kullanabilirsiniz.

Çoklu İşlem

Bunu bir dosyaya koyun - 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()

Ve işte çıktı:

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

Çok iş parçacığı

Şimdi değiştirmek ProcessPoolExecutoriçin ThreadPoolExecutorve tekrar modülü çalıştırın:

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

Şimdi hem çoklu iş parçacığı hem de çok işlemciliği yaptınız!

Performans ve her ikisini birlikte kullanma hakkında not.

Örnekleme sonuçları karşılaştırmak için çok küçük.

Ancak, Windows'un çatal işlemeyi desteklemediğinden, her yeni işlemin başlatılması zaman alması gerektiğinden, çoklu iş parçacığının genel olarak çoklu işlemden daha hızlı olacağından şüpheleniyorum. Linux veya Mac'te muhtemelen daha yakın olacaklar.

Birden çok işlemi birden çok işlemin içine yerleştirebilirsiniz, ancak birden çok işlemi kapatmak için birden çok iş parçacığı kullanmamanız önerilir.


ThreadPoolExecutor GIL tarafından uygulanan sınırlamaları atlıyor mu? Ayrıca, yürütücülerin bitmesini beklemek için () 'a katılmanız gerekmeyecek ya da bu, örtük olarak bağlam yöneticisi içinde ele alınıyor
PirateApp

1
Hayır ve hayır, evet "örtük olarak ele alındı"
Aaron Hall

Bazı nedenlerden dolayı, sorunu ölçeklendirirken, çoklu iş parçacığı son derece hızlıdır, ancak çok işlemcili bir sürü sıkışmış işlemi (macOS'ta) ortaya çıkarır. Bunun neden olabileceğine dair bir fikrin var mı? Süreç sadece iç içe döngüler ve matematik içerir, egzotik bir şey değildir.
komodovaran_

@komodovaran_ Bir işlem, her biri bir tane olmak üzere tam bir Python işlemidir, bir iş parçacığı ise işlemi, bayt kodunu ve bellekteki diğer tüm iş parçacıklarıyla paylaştığı kendi yığınıyla yalnızca bir yürütme iş parçacığına sahiptir. ?
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)

Yukarıdaki benim makinemde güzel çalışıyor (Ubuntu, paket joblib önceden kurulmuş, ancak üzerinden kurulabilir pip install joblib).

Alındığı https://blog.dominodatalab.com/simple-parallelization/


3
Kodunuzu denedim ama sistemimde bu kodun sıralı sürümü yaklaşık yarım dakika sürer ve yukarıdaki paralel sürüm 4 dakika sürer. Neden öyle?
shaifali Gupta

3
Cevabınız için teşekkürler! 2019'da bunu yapmanın en zarif yolu olduğunu düşünüyorum.
Heikki Pulkkinen

2
çoklu işlem Python 3.x için geçerli değil, bu yüzden benim için çalışmıyor.
EngrStudent

2
@EngrStudent "Geçerli değil" ile ne demek istediğinizden emin değilim. Benim için Python 3.6.x için çalışıyor.
tyrex

@tyrex paylaşım için teşekkürler! Bu joblib paketi harika ve örnek benim için çalışıyor. Yine de, daha karmaşık bir bağlamda ne yazık ki bir hata vardı. github.com/joblib/joblib/issues/949
Açık Yemek Komisyoncusu

13

Ray kullanmanın birçok avantajı vardır :

  • Birden fazla çekirdeğe ek olarak birden fazla makineye paralel olarak paralel yapabilirsiniz (aynı kodla).
  • Paylaşılan bellek (ve sıfır kopya serileştirme) yoluyla sayısal verilerin verimli kullanımı.
  • Dağıtılmış zamanlama ile yüksek iş hacmi.
  • Hata toleransı.

Sizin durumunuzda Ray'i başlatabilir ve bir uzak fonksiyon tanımlayabilirsiniz

import ray

ray.init()

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

ve paralel olarak çağır

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)

Aynı örneği bir kümede çalıştırmak için, değişecek tek satır ray.init () çağrısı olacaktır. İlgili belgeleri burada bulabilirsiniz .

Ray'i geliştirmeye yardım ettiğimi unutmayın.


1
Ray'i düşünen herkes için, Windows'un yerel olarak desteklenmediğini bilmek uygun olabilir. WSL'yi (Linux için Windows Alt Sistemi) kullanarak Windows'ta çalışmasını sağlamak için bazı hack'ler mümkündür, ancak Windows'u kullanmak istiyorsanız kutudan çıkmaz.
OscarVanL

9

Bunu yapmanın en kolay yolu budur!

Asyncio'yu kullanabilirsiniz . (Belgeleri burada bulabilirsiniz ). Yüksek performanslı ağ ve web sunucuları, veritabanı bağlantı kitaplıkları, dağıtılmış görev kuyrukları, vb. Sağlayan birden fazla Python asenkron çerçevesinin temeli olarak kullanılır. .

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

Şimdi bu fonksiyon çağrıldığında ana programı bekleme durumuna getirmeden paralel olarak çalışacaktır. Döngü için de paralelleştirmek için kullanabilirsiniz. For döngüsü çağrıldığında, döngü sıralı olsa da, yorumlayıcı oraya ulaşır ulaşmaz her yineleme ana programa paralel olarak çalışır. Örneğin:

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

Bu aşağıdaki çıktıyı üretir:

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

Bence bir yazım hatası var wrapped()ve bunun **kwargsyerine olmalı*kwargs
jakub-olczyk

Hata! Benim hatam. Düzeltilmiş!
Kullanıcı5

6

Neden global bir listeyi korumak için evreler ve bir muteks kullanmıyorsunuz?

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

unutmayın, en yavaş ipliğiniz kadar hızlı olacaksınız


2
Bunun çok eski bir cevap olduğunu biliyorum, bu yüzden hiçbir yerden rastgele bir düşüş elde etmek bir serseri. Ben sadece iş parçacığı hiçbir şey paralel olmayacak çünkü aşağı düştü. Python'daki iş parçacıkları, global yorumlayıcı kilidi nedeniyle yorumlayıcıda aynı anda yalnızca bir iş parçacığına bağlanır, bu nedenle eşzamanlı programlamayı destekler , ancak OP'nin istediği gibi paralel değildir .
skrrgwasme

3
@skrrgwasme Bunu bildiğinizi biliyorum, ama "hiçbir şeyi paralelleştirmeyecekler" sözcüklerini kullandığınızda, bu okuyucuları yanlış yönlendirebilir. Operasyonlar IO'ya bağlı oldukları veya bir olayı beklerken uyudukları için uzun zaman alıyorsa, yorumlayıcı diğer konuları çalıştırmak için serbest bırakılır, bu da insanların bu durumlarda umduğu hız artışına neden olur. Skrrgwasme'nin söylediklerinden yalnızca CPU bağlantılı iş parçacıkları gerçekten etkilenir.
Jonathan Hartley

5

Benimle joblibçok faydalı buldum . Lütfen aşağıdaki örneğe bakın:

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: mevcut tüm çekirdekleri kullanın


14
Kendi cevaplarınızı göndermeden önce mevcut cevapları kontrol etmek daha iyidir. Bu cevap aynı zamanda kullanmayı önermektedir joblib.
sanyash

2

Diyelim ki bir asenkron fonksiyonumuz var

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

Bunun büyük bir dizide çalıştırılması gerekiyor. Bazı öznitelikler programa aktarılırken, bazıları dizideki sözlük öğesinin özelliğinden kullanılır.

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

Şuna bir bakın;

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

Bunu yapmanın doğru yolu olmayabilir, ama şöyle bir şey yaparım;

Gerçek kod;

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

Umarım yardımcı olur.


1

Bu, Python'da çok işlemli ve paralel / dağıtılmış bilgi işlem yaparken faydalı olabilir.

Techila paketini kullanma hakkında YouTube eğitimi

Techila, techila paketini kullanarak doğrudan Python ile entegre olan dağıtılmış bir bilgi işlem aracıdır. Paketteki şeftali işlevi, döngü yapılarını paralel hale getirmede yararlı olabilir. (Aşağıdaki kod snippet'i Techila Topluluk Forumlarından alınmıştır )

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
Bu bağlantı soruyu cevaplayabilirken, cevabın önemli kısımlarını buraya eklemek ve bağlantıyı referans olarak sağlamak daha iyidir. Bağlantı verilen sayfa değişirse, yalnızca bağlantı yanıtları geçersiz olabilir.
SL Barth - Monica'yı

2
@SLBarth geri bildiriminiz için teşekkür ederiz. Cevaba küçük bir örnek kod ekledim.
TEE

1

teşekkürler @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. Bu sadece kodlu bir cevaptır. Okuyuculara yayınladığınız kodun ne yaptığını ve belki de ek bilgileri nerede bulabileceklerini anlatan bir açıklama eklemenizi öneririm.
starbeamrainbowlabs

-1

paralel işlemenin çok basit bir örneği

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
Burada for döngüsünde paralellik yoktur, sadece tüm döngüyü çalıştıran bir süreç ortaya çıkarırsınız; Bu OP'nin amaçladığı şey DEĞİLDİR.
facuq
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.