मैं एक साधारण पायथन लूप को कैसे समानांतर करूं?


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)

मुझे पता है कि पायथन में सिंगल थ्रेड कैसे शुरू करें, लेकिन मुझे नहीं पता कि परिणामों को कैसे "इकट्ठा" किया जाए।

एकाधिक प्रक्रियाएं भी ठीक होंगी - जो भी इस मामले के लिए सबसे आसान है। मैं वर्तमान में लिनक्स का उपयोग कर रहा हूं, लेकिन कोड को विंडोज और मैक पर भी चलना चाहिए।

इस कोड को समानांतर करने का सबसे आसान तरीका क्या है?

जवाबों:


191

CPython पर कई थ्रेड्स का उपयोग करने से आपको वैश्विक दुभाषिया लॉक (GIL) के कारण शुद्ध-पायथन कोड के लिए बेहतर प्रदर्शन नहीं मिलेगा। मैं multiprocessingइसके बजाय मॉड्यूल का उपयोग करने का सुझाव देता हूं :

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

ध्यान दें कि यह इंटरएक्टिव इंटरप्रेटर में काम नहीं करेगा।

GIL के आसपास सामान्य FUD से बचने के लिए: वैसे भी इस उदाहरण के लिए थ्रेड्स का उपयोग करने का कोई लाभ नहीं होगा। आप यहां प्रक्रियाओं का उपयोग करना चाहते हैं, न कि थ्रेड्स, क्योंकि वे समस्याओं के एक पूरे समूह से बचते हैं।


46
चूंकि यह चुना हुआ उत्तर है, क्या इसका अधिक व्यापक उदाहरण होना संभव है? के तर्क क्या हैं calc_stuff?
एडुआर्डो पिगनैटेली

2
@EduardoPignatelli कृपया multiprocessingअधिक व्यापक उदाहरणों के लिए मॉड्यूल के प्रलेखन को पढ़ें । Pool.map()मूल रूप से काम करता है map(), लेकिन समानांतर में।
स्वेन मार्नाच

3
क्या कोड की संरचना में एक tqdm लोडिंग बार में बस जोड़ने का कोई तरीका है? मैंने tqdm (pool.imap (calc_stuff, रेंज (0, 10 * ऑफसेट, ऑफसेट)) का उपयोग किया है), लेकिन मुझे पूर्ण लोडिंग बार ग्राफ़िक नहीं मिलता है।
user8188120

@ user8188120 मैंने पहले कभी tqdm के बारे में नहीं सुना है, इसलिए क्षमा करें, मैं इसके साथ मदद नहीं कर सकता।
स्वेन मार्नाच

Tqdm लोडिंग बार के लिए यह प्रश्न देखें: stackoverflow.com/questions/41920124/…
जोहान्स

66

लूप के लिए एक सरल को समानांतर करने के लिए, जॉर्लिब मल्टीप्रोसेसिंग के कच्चे उपयोग के लिए बहुत अधिक मूल्य लाता है। न केवल छोटे वाक्यविन्यास, बल्कि यह भी कि जब वे बहुत तेज़ होते हैं (बच्चे के ऊपर की प्रक्रिया को हटाने के लिए) या बच्चे की प्रक्रिया के ट्रेसबैक पर कब्जा करने के लिए पुनरावृत्तियों के पारदर्शी गुच्छन जैसी चीजें होती हैं, तो बेहतर त्रुटि रिपोर्टिंग होती है।

डिस्क्लेमर: मैं जॉबलिब का मूल लेखक हूं।


1
मैंने जॉब के साथ जॉब करने की कोशिश की, यह काम नहीं कर रहा है। समानांतर-विलंबित कॉल के बाद, पृष्ठ ने काम करना बंद कर दिया।
जेई

1
नमस्ते, मुझे joblib ( stackoverflow.com/questions/52166572/… ) का उपयोग करने में समस्या है , क्या आपके पास कोई कारण है कि क्या कारण हो सकता है? बहुत बहुत धन्यवाद।
तिंग सुन

लगता है कि मैं एक शॉट देना चाहता हूँ! क्या यह संभव है कि मैं एक डबल लूप के साथ इसका उपयोग
करूं

51

इस कोड को समानांतर करने का सबसे आसान तरीका क्या है?

मुझे वास्तव में यह पसंद concurrent.futuresहै, 3.2 संस्करण के बाद से Python3 में उपलब्ध है - और PyPi पर 2.6 और 2.7 के लिए बैकपोर्ट के माध्यम से ।

आप थ्रेड्स या प्रक्रियाओं का उपयोग कर सकते हैं और सटीक उसी इंटरफ़ेस का उपयोग कर सकते हैं।

बहु

इसे किसी फ़ाइल में डालें - 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]

बहु सूत्रण

अब बदलने ProcessPoolExecutorके लिए ThreadPoolExecutor, और मॉड्यूल को फिर से चलाने:

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

अब आपने मल्टीथ्रेडिंग और मल्टीप्रोसेसिंग दोनों किया है!

प्रदर्शन और दोनों का एक साथ उपयोग करने पर ध्यान दें।

परिणामों की तुलना करने के लिए नमूनाकरण बहुत छोटा है।

हालांकि, मुझे संदेह है कि मल्टीथ्रेडिंग सामान्य रूप से विंडोज पर मल्टीप्रोसेसिंग की तुलना में अधिक तेज होगी, क्योंकि विंडोज फोर्किंग का समर्थन नहीं करता है इसलिए प्रत्येक नई प्रक्रिया को लॉन्च होने में समय लगता है। लिनक्स या मैक पर वे शायद करीब होंगे।

आप कई प्रक्रियाओं के अंदर कई थ्रेड्स को घोंसला बना सकते हैं, लेकिन कई प्रक्रियाओं को बंद करने के लिए कई थ्रेड्स का उपयोग नहीं करने की सिफारिश की जाती है।


क्या ThreadPoolExecutor GIL द्वारा लगाई गई सीमाओं को बायपास करता है? निष्पादकों के समाप्त होने की प्रतीक्षा करने के लिए भी आपको शामिल होने की आवश्यकता नहीं है (या संदर्भ प्रबंधक के अंदर स्पष्ट रूप से ध्यान रखने योग्य है
PirateApp

1
कोई और नहीं, हाँ करने के लिए "परोक्ष संभाला"
हारून हॉल

किसी कारण से, जब समस्या को बढ़ाते हैं, तो मल्टीथ्रेडिंग बहुत तेज होती है, लेकिन मल्टीप्रोसेसिंग अटक प्रक्रियाओं के एक गुच्छा (मैकओएस में) को जन्म देती है। किसी भी विचार क्यों हो सकता है? इस प्रक्रिया में केवल नेस्टेड लूप और गणित शामिल हैं, विदेशी कुछ भी नहीं।
कोमोडोवरन_

@komodovaran_ एक प्रक्रिया एक पूर्ण पायथन प्रक्रिया है, प्रत्येक में से एक है, जबकि एक धागा सिर्फ अपने स्वयं के ढेर के साथ निष्पादन का एक धागा है जो प्रक्रिया को साझा करता है, इसके बाइटकोड और बाकी सभी थ्रेड के साथ स्मृति में यह सब कुछ करता है - जो उस मदद करता है ?
हारून हॉल

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)

ऊपर मेरी मशीन पर खूबसूरती से काम करता है (उबंटू, पैकेज जॉबलिब पूर्व-स्थापित था, लेकिन इसके माध्यम से स्थापित किया जा सकता है pip install joblib)।

Https://blog.dominodatalab.com/simple-parallelization/ से लिया गया


3
मैंने आपके कोड की कोशिश की, लेकिन मेरे सिस्टम पर इस कोड के अनुक्रमिक संस्करण में लगभग आधा मिनट लगता है और उपरोक्त समानांतर संस्करण में 4 मिनट लगते हैं। ऐसा क्यों?
शैफाली गुप्ता

3
आपके उत्तर के लिए धन्यवाद! मुझे लगता है कि 2019 में ऐसा करने का यह सबसे सुंदर तरीका है।
हिक्की पुलकिनन

2
मल्टीप्रोसेसिंग पायथन 3.x के लिए मान्य नहीं है, इसलिए यह मेरे लिए काम नहीं करता है।
EngrStudent

2
@EngrStudent सुनिश्चित नहीं है कि "मान्य नहीं" से आपका क्या मतलब है। यह मेरे लिए अजगर 3.6.x के लिए काम करता है।
टाइरेक्स

साझा करने के लिए @tyrex धन्यवाद! यह जॉबलिब पैकेज बढ़िया है और उदाहरण मेरे लिए काम करता है। हालांकि, अधिक जटिल संदर्भ में मेरे पास दुर्भाग्य से एक बग था। github.com/joblib/joblib/issues/949
ओपन फूड ब्रोकर

13

रे का उपयोग करने के कई फायदे हैं :

  • आप कई कोर (एक ही कोड के साथ) के अलावा कई मशीनों पर समानांतर कर सकते हैं।
  • साझा मेमोरी (और शून्य-प्रतिलिपि क्रमांकन) के माध्यम से संख्यात्मक डेटा की कुशल हैंडलिंग।
  • वितरित शेड्यूलिंग के साथ उच्च कार्य थ्रूपुट।
  • दोष सहिष्णुता।

आपके मामले में, आप रे शुरू कर सकते हैं और एक दूरस्थ फ़ंक्शन को परिभाषित कर सकते हैं

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 ()। प्रासंगिक प्रलेखन यहां पाया जा सकता है

ध्यान दें कि मैं रे को विकसित करने में मदद कर रहा हूं।


1
किरण पर विचार करने वाले किसी व्यक्ति के लिए, यह जानना प्रासंगिक हो सकता है कि यह मूल रूप से विंडोज का समर्थन नहीं करता है। WSL (लिनक्स के लिए विंडोज सबसिस्टम) का उपयोग करके विंडोज में काम करने के लिए कुछ हैक संभव हैं, हालांकि यदि आप विंडोज का उपयोग करना चाहते हैं तो यह मुश्किल से आउट-ऑफ-बॉक्स है।
ऑस्करवन

9

यह इसे करने का सबसे आसान तरीका है!

आप एसिंको का उपयोग कर सकते हैं । (यहां प्रलेखन पाया जा सकता है )। इसका उपयोग कई पायथन अतुल्यकालिक रूपरेखाओं के लिए एक नींव के रूप में किया जाता है जो उच्च-प्रदर्शन नेटवर्क और वेब-सर्वर, डेटाबेस कनेक्शन लाइब्रेरी, वितरित कार्य कतार इत्यादि प्रदान करते हैं। साथ ही इसमें किसी भी प्रकार की समस्या के साथ उच्च-स्तरीय और निम्न-स्तर दोनों एपीआई हैं। ।

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

अब जब भी मुख्य कार्यक्रम को प्रतीक्षा अवस्था में रखे बिना बुलाया जाएगा तो यह समारोह समानांतर रूप से चलाया जाएगा। आप इसे लूप के लिए भी समानांतर बनाने के लिए उपयोग कर सकते हैं। जब एक लूप के लिए बुलाया जाता है, हालांकि लूप अनुक्रमिक होता है लेकिन हर पुनरावृत्ति मुख्य कार्यक्रम के समानांतर चलता है जैसे ही दुभाषिया वहां पहुंचता है। उदाहरण के लिए:

@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
याकूब-ऑल्केज़िक

ऊप्स! मेरी गलती। सही किया!
उपयोगकर्ता 5

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
मुझे पता है कि यह एक बहुत पुराना उत्तर है, इसलिए यह कहीं से भी बेतरतीब ढंग से नीचे उतरने का बमर है। मैंने केवल इसलिए डाउनवोट किया क्योंकि धागे कुछ भी समानांतर नहीं करेंगे। पाइथन में थ्रेड्स एक समय में इंटरप्रेटर पर निष्पादित होने वाले केवल एक धागे से बंधे होते हैं क्योंकि वैश्विक दुभाषिया लॉक होता है, इसलिए वे समवर्ती प्रोग्रामिंग का समर्थन करते हैं, लेकिन ओपी के अनुरोध के समानांतर नहीं
skrrgwasme

3
@skrgwasme मुझे पता है कि आप यह जानते हैं, लेकिन जब आप "वे कुछ भी समानांतर नहीं करेंगे" शब्दों का उपयोग करते हैं, तो यह पाठकों को भ्रमित कर सकता है। यदि ऑपरेशन में लंबा समय लगता है क्योंकि वे IO बाउंड होते हैं, या किसी घटना का इंतजार करते हुए सो जाते हैं, तो दुसरे सूत्र को चलाने के लिए दुभाषिया को मुक्त कर दिया जाता है, इसलिए इससे उन मामलों में गति बढ़ने की उम्मीद लोग कर रहे हैं। केवल सीपीआर बाउंड थ्रेड्स वास्तव में प्रभावित होते हैं जो स्कर्गवास्मे कहते हैं।
जोनाथन हार्टले

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
संन्यास

2

मान लीजिए कि हमारे पास एक async फ़ंक्शन है

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 पैकेज का उपयोग करके पायथन के साथ एकीकृत होता है। पैकेज में पीच फ़ंक्शन लूप संरचनाओं को समानांतर करने में उपयोगी हो सकता है। (निम्नलिखित कोड स्निपेट टेकिला सामुदायिक मंच से है )

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
हालांकि यह लिंक प्रश्न का उत्तर दे सकता है, लेकिन उत्तर के आवश्यक भागों को शामिल करना और संदर्भ के लिए लिंक प्रदान करना बेहतर है। लिंक-केवल उत्तर अमान्य हो सकते हैं यदि लिंक किए गए पृष्ठ बदल जाते हैं।
एसएल बर्थ -

2
@SLBarth ने प्रतिक्रिया के लिए धन्यवाद दिया। मैंने उत्तर में एक छोटा सा नमूना कोड जोड़ा।
तीज

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। यह एक कोड-ओनली उत्तर है। मैं एक स्पष्टीकरण जोड़ने का सुझाव दूंगा जो पाठकों को बताता है कि आपके द्वारा पोस्ट किया गया कोड क्या करता है, और शायद जहां वे अतिरिक्त जानकारी प्राप्त कर सकते हैं।
स्टारबिम्रेनबोलाब्स

-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
यहां लूप के लिए कोई समानता नहीं है, आप सिर्फ एक प्रक्रिया को पैदा कर रहे हैं जो पूरे लूप को चलाता है; यह ओपी का इरादा नहीं है।
फेसुक्क
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.