मल्टीप्रोसेसिंग प्रक्रियाओं के बीच साझा करें बड़े, केवल-पढ़ने योग्य गांठ


88

मेरे पास एक 60GB SciPy Array (मैट्रिक्स) है जिसे मुझे 5+ multiprocessing Processवस्तुओं के बीच साझा करना चाहिए । मैंने सुन्न-साझा देखा और SciPy सूची पर इस चर्चा को पढ़ा । वहाँ दो दृष्टिकोण लग रहे हैं - numpy-sharedmemऔर एक multiprocessing.RawArray()और मैपिंग करने के लिए NumPy dtypes का उपयोग करना ctype। अब, numpy-sharedmemजाने का रास्ता प्रतीत होता है, लेकिन मुझे एक अच्छा संदर्भ उदाहरण देखना बाकी है। मुझे किसी भी प्रकार के ताले की आवश्यकता नहीं है, क्योंकि सरणी (वास्तव में एक मैट्रिक्स) केवल-पढ़ने के लिए होगी। अब, इसके आकार के कारण, मैं एक प्रति से बचना चाहूंगा। ऐसा लगता है कि सही विधि एक सरणी के रूप में सरणी की एकमात्र प्रतिलिपि बनाने के लिए है sharedmem, और फिर इसे Processवस्तुओं को पास करें? कुछ विशिष्ट प्रश्न:

  1. वास्तव में सब-एसईएम को साझा करने के लिए सबसे अच्छा तरीका क्या है Process()? क्या मुझे केवल एक सरणी को पास करने के लिए एक कतार की आवश्यकता है? क्या एक पाइप बेहतर होगा? क्या मैं इसे Process()उपवर्ग के तर्क के तर्क के रूप में पारित कर सकता हूं (जहां मैं इसे ग्रहण कर रहा हूं)?

  2. ऊपर चर्चा में, मैं numpy-sharedmem64 बिट सुरक्षित नहीं होने का उल्लेख है ? मैं निश्चित रूप से कुछ संरचनाओं का उपयोग कर रहा हूं जो 32-बिट पते योग्य नहीं हैं।

  3. वहाँ व्यापार के RawArray()दृष्टिकोण के लिए कर रहे हैं? धीमी, बगेरियर?

  4. क्या मुझे सुन्न-साममी विधि के लिए किसी भी ctype-to-dtype मानचित्रण की आवश्यकता है?

  5. क्या किसी के पास ऐसा कुछ OpenSource कोड का उदाहरण है? मैं बहुत पढ़ा हुआ हूं और यह देखने के लिए किसी भी तरह के अच्छे उदाहरण के बिना काम करना मुश्किल है।

यदि कोई अतिरिक्त जानकारी है, तो मैं इसे दूसरों के लिए स्पष्ट करने में मदद कर सकता हूं, कृपया टिप्पणी करें और मैं जोड़ दूंगा। धन्यवाद!

इसके लिए उबंटू लिनक्स और शायद मैक ओएस पर चलना होगा , लेकिन पोर्टेबिलिटी एक बड़ी चिंता नहीं है।


1
यदि विभिन्न प्रक्रियाएँ उस सरणी में लिखने जा रही हैं, multiprocessingतो प्रत्येक प्रक्रिया के लिए पूरी चीज़ की प्रतिलिपि बनाने की अपेक्षा करें।
तियागो जूल

3
@ इटियागो: "मुझे किसी भी तरह के ताले की ज़रूरत नहीं है, क्योंकि सरणी (वास्तव में एक मैट्रिक्स) केवल-पढ़ने के लिए होगी"
डॉ। जन-फिलिप गेहरके

1
@ इटियागो: भी, जब तक स्पष्ट रूप से (तर्कों के माध्यम से target_function) नहीं बताया जाता है, मल्टीप्रोसेसिंग एक प्रति नहीं बना रहा है । ऑपरेटिंग सिस्टम केवल संशोधन पर बच्चे की मेमोरी स्पेस में माता-पिता की मेमोरी के कुछ हिस्सों को कॉपी करने जा रहा है।
डॉ। जन-फिलिप गेर्के


मैंने पहले इस बारे में कुछ सवाल पूछे । मेरा समाधान यहां पाया जा सकता है: github.com/david-hoffman/peaks/blob/… (क्षमा करें, कोड एक आपदा है)।
डेविड हॉफमैन

जवाबों:


30

@Velimir Mlaker ने शानदार जवाब दिया। मैंने सोचा कि मैं टिप्पणियों के कुछ टुकड़े और एक छोटा उदाहरण जोड़ सकता हूं।

(मैं साझाकरण पर बहुत दस्तावेज नहीं पा सका - ये मेरे अपने प्रयोगों के परिणाम हैं।)

  1. क्या आपको उपप्रकार शुरू होने पर या इसके शुरू होने के बाद हैंडल को पास करने की आवश्यकता है? यदि यह सिर्फ पूर्व का है, तो आप इसके लिए targetऔर argsतर्कों का उपयोग कर सकते हैं Process। वैश्विक चर का उपयोग करने की तुलना में यह संभावित रूप से बेहतर है।
  2. आपके द्वारा लिंक किए गए चर्चा पृष्ठ से, ऐसा प्रतीत होता है कि 64-बिट लिनक्स के लिए समर्थन थोड़ी देर पहले शेयरमैम में जोड़ा गया था, इसलिए यह एक गैर-मुद्दा हो सकता है।
  3. मैं इस बारे में नहीं जानता।
  4. नीचे उदाहरण के लिए देखें।

उदाहरण

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __name__ == '__main__':
    split_work(4)

उत्पादन

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

यह संबंधित प्रश्न उपयोगी हो सकता है।


37

यदि आप लिनक्स (या किसी POSIX- अनुरूप प्रणाली) पर हैं, तो आप इस सरणी को एक वैश्विक चर के रूप में परिभाषित कर सकते हैं। लिनक्स पर multiprocessingउपयोग fork()कर रहा है जब यह एक नई बच्चे की प्रक्रिया शुरू करता है। एक नवनिर्मित बच्चे की प्रक्रिया अपने माता-पिता के साथ स्मृति को स्वचालित रूप से साझा करती है जब तक कि वह इसे नहीं बदलता ( कॉपी-ऑन-राइट तंत्र)।

जब से आप कह रहे हैं "मुझे किसी भी प्रकार के ताले की आवश्यकता नहीं है, क्योंकि सरणी (वास्तव में एक मैट्रिक्स) केवल-पढ़ने के लिए होगा" इस व्यवहार का लाभ उठाना बहुत ही सरल और अभी तक अत्यंत कुशल दृष्टिकोण होगा: सभी बाल प्रक्रियाओं तक पहुंच होगी भौतिक स्मृति में एक ही डेटा जब इस बड़े संख्यात्मक सरणी को पढ़ता है।

करने के लिए अपने सरणी हाथ मत करो Process()निर्माता, इस निर्देश देगा multiprocessingकरने के लिए pickleबच्चे के लिए डेटा है, जो अत्यंत अक्षम या अपने मामले में असंभव हो जाएगा। लिनक्स पर, fork()बच्चे के ठीक बाद उसी भौतिक स्मृति का उपयोग करके माता-पिता की एक सटीक प्रतिलिपि होती है, इसलिए आपको बस इतना करना सुनिश्चित करना है कि पायथन चर 'मैट्रिक्स' युक्त targetफ़ंक्शन उस फ़ंक्शन के भीतर से सुलभ है जिसे आप सौंप देते हैं Process()। यह आप आमतौर पर एक 'वैश्विक' चर के साथ प्राप्त कर सकते हैं।

उदाहरण कोड:

from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

विंडोज पर - जो समर्थन नहीं करता है fork()- multiprocessingwin32 एपीआई कॉल का उपयोग कर रहा है CreateProcess। यह किसी भी दिए गए निष्पादन योग्य से एक पूरी तरह से नई प्रक्रिया बनाता है। यही कारण है कि अगर किसी को माता-पिता के रनटाइम के दौरान बनाए गए डेटा की आवश्यकता होती है, तो विंडोज पर बच्चे को डेटा अचार करना आवश्यक है।


3
कॉपी-ऑन-राइट, रेफरेंस काउंटर वाले पेज को कॉपी करेगा (इसलिए प्रत्येक कांटे वाले अजगर का अपना रेफरेंस काउंटर होगा) लेकिन यह पूरे डेटा ऐरे को कॉपी नहीं करेगा।
लुटेरा

1
मुझे लगता है कि मैं वैश्विक चर के साथ की तुलना में मॉड्यूल स्तर चर के साथ और अधिक सफलता मिली है ... यानी कांटा से पहले वैश्विक दायरे में एक मॉड्यूल के लिए चर जोड़ने
लुटेरा

5
इस प्रश्न / उत्तर में ठोकर खाने वाले लोगों के लिए सावधानी का एक शब्द: यदि आप अपने मल्टीथ्रेडेड ऑपरेशन के लिए ओपनबीएलएएस-लिंक्ड नेम्पी का उपयोग करते हुए होते हैं, तो इसके मल्टीथ्रेडिंग (निर्यात OPENBLAS_NUM_TH_ADADS = 1) को अक्षम करना सुनिश्चित करें जब उपयोग multiprocessingया बच्चे की प्रक्रिया फांसी हो सकती है () आमतौर पर एक साझा वैश्विक सरणी / मैट्रिक्स पर रैखिक बीजगणित संचालन करने पर n प्रोसेसर के बजाय एक प्रोसेसर के 1 / n का उपयोग किया जाता है । OpenBLAS साथ जाना जाता थ्रेड संघर्ष अजगर को विस्तार करने के लिए लगता हैmultiprocessing
Dologan

1
क्या कोई समझा सकता है कि अजगर सिर्फ सीरियल देने के बजाय, forkदिए गए मापदंडों को पारित करने के लिए ओएस का उपयोग क्यों नहीं करेगा Process? यही है, forkमूल प्रक्रिया के लिए लागू नहीं किया जा सकता है पहले child कहा जाता है, ताकि पैरामीटर मान अभी भी ओएस से उपलब्ध है? इसे क्रमबद्ध करने से अधिक कुशल प्रतीत होगा?
अधिकतम

2
हम सभी जानते हैं कि fork()विंडोज पर उपलब्ध नहीं है, यह मेरे उत्तर में और कई बार टिप्पणियों में कहा गया है। मुझे पता है कि यह अपने प्रारंभिक सवाल था, और मैं इसे ऊपर चार टिप्पणियां जवाब इस : "समझौता डिफ़ॉल्ट रूप से दोनों प्लेटफार्मों पर पैरामीटर हस्तांतरण, बेहतर रख-रखाव के लिए और समान व्यवहार सुनिश्चित करने के लिए की एक ही विधि का उपयोग करने के लिए है।"। दोनों तरीकों के अपने फायदे और नुकसान हैं, यही वजह है कि पायथन 3 में उपयोगकर्ता के लिए विधि का चयन करने के लिए अधिक लचीलापन है। यह चर्चा उत्पादक डब्ल्यू / ओ बात कर विवरण नहीं है, जो हमें यहां नहीं करना चाहिए।
डॉ। जन-फिलिप गेर्के

24

आपके द्वारा लिखे गए कोड के एक छोटे टुकड़े में आपकी रुचि हो सकती है: github.com/vmlaker/benchmark-saredmem

ब्याज की एकमात्र फाइल है main.py। इसके बारे में एक बेंचमार्क है numpy-sharedmem - कोड बस सरणियों (या तो गुजरता है numpyया sharedmemपाइप के माध्यम से,) पैदा की प्रक्रियाओं के लिए। कार्यकर्ता सिर्फ sum()डेटा पर कॉल करते हैं। मुझे केवल दो कार्यान्वयन के बीच डेटा संचार समय की तुलना करने में दिलचस्पी थी।

मैंने एक और, अधिक जटिल कोड: github.com/vmlaker/sherlock भी लिखा ।

यहाँ मैं OpenCV के साथ वास्तविक समय की छवि प्रसंस्करण के लिए संख्यात्मक-शेप्ड मॉडेम का उपयोग करता हूं - OpenCV के नए cv2एपीआई के अनुसार, इमेज न्यूपे सरणियाँ हैं । चित्र, वास्तव में इसके संदर्भ में, डिक्शनरी ऑब्जेक्ट के माध्यम से साझा किए जाते हैं multiprocessing.Manager(क्यू या पाइप का उपयोग करने के विपरीत)।

पाइप बनाम कतार :

मेरे अनुभव में, पाइप के साथ आईपीसी कतार से तेज है। और यह समझ में आता है, क्योंकि क्यू कई निर्माताओं / उपभोक्ताओं के लिए इसे सुरक्षित बनाने के लिए लॉकिंग जोड़ता है। पाइप नहीं है। लेकिन अगर आपके पास आगे-पीछे की बात करने वाली केवल दो प्रक्रियाएँ हैं, तो पाइप का उपयोग करना सुरक्षित है, या, जैसा कि डॉक्स ने पढ़ा है:

... एक ही समय में पाइप के विभिन्न सिरों का उपयोग करने वाली प्रक्रियाओं से भ्रष्टाचार का कोई खतरा नहीं है।

sharedmemसुरक्षा :

sharedmemमॉड्यूल के साथ मुख्य मुद्दा अप्रिय कार्यक्रम से बाहर निकलने पर मेमोरी लीक की संभावना है। यह यहाँ एक लंबी चर्चा में वर्णित है । हालांकि 10 अप्रैल, 2011 को स्टर्ला ने मेमोरी लीक को ठीक करने का उल्लेख किया है, फिर भी मैंने तब से लीक का अनुभव किया है, जब दोनों रिपॉज का उपयोग कर रहे हैं, स्टिटला मोल्डेन का खुद का GitHub ( github.com/sturlamolden/sarededem-numpy ) और क्रिस ली-मेस्सर पर बिटबकैट ( bitbucket.org/cleemesser/numpy-saredmem )।


धन्यवाद, बहुत बहुत जानकारीपूर्ण। स्मृति sharedmemएक बड़ी बात की तरह लगता है, हालांकि रिसाव । कोई भी हल करने की ओर जाता है?
विल

1
सिर्फ लीक को नोटिस करने के अलावा, मैंने कोड में इसकी तलाश नहीं की है। मैंने अपने जवाब में, "साझा सुरक्षा" के तहत sharedmem, संदर्भ के लिए मॉड्यूल के दो ओपन सोर्स रेपो के तहत जोड़ा ।
वेलिमेर म्लेकर

14

अगर आपकी सरणी इतनी बड़ी है तो आप इसका उपयोग कर सकते हैं numpy.memmap। उदाहरण के लिए, यदि आपके पास डिस्क में संग्रहीत एक सरणी है, तो कहें 'test.array', आप "लेखन" मोड में भी डेटा तक पहुंचने के लिए एक साथ प्रक्रियाओं का उपयोग कर सकते हैं, लेकिन आपका मामला सरल है क्योंकि आपको केवल "रीडिंग" मोड की आवश्यकता है।

सरणी बनाना:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

आप इस सरणी को उसी तरह भर सकते हैं जैसे आप एक साधारण सरणी के साथ करते हैं। उदाहरण के लिए:

a[:10,:100]=1.
a[10:,100:]=2.

जब आप वैरिएबल हटाते हैं, तो डेटा डिस्क में संग्रहीत होता है a

बाद में आप कई प्रक्रियाओं का उपयोग कर सकते हैं जो डेटा में पहुंचेंगे test.array:

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

संबंधित उत्तर:


3

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


0

मल्टीथ्रेडिंग का उपयोग क्यों नहीं किया जाता है? मुख्य प्रक्रिया के संसाधनों को मूल रूप से इसके थ्रेड्स द्वारा साझा किया जा सकता है, इस प्रकार मल्टीथ्रेडिंग मुख्य प्रक्रिया के स्वामित्व वाली वस्तुओं को साझा करने का एक बेहतर तरीका है।

यदि आप अजगर के जीआईएल तंत्र के बारे में चिंता करते हैं, तो शायद आप इसका सहारा ले सकते nogilहैं numba

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.