मल्टीप्रोसेसिंग के लिए साझा मेमोरी में सुन्न सरणी का उपयोग करें


111

मैं मल्टीप्रोसेसिंग मॉड्यूल के साथ उपयोग के लिए साझा मेमोरी में एक संख्यात्मक सरणी का उपयोग करना चाहूंगा। कठिनाई इसे एक संख्यात्मक सरणी की तरह उपयोग कर रही है, और केवल ctypes सरणी के रूप में नहीं।

from multiprocessing import Process, Array
import scipy

def f(a):
    a[0] = -a[0]

if __name__ == '__main__':
    # Create the array
    N = int(10)
    unshared_arr = scipy.rand(N)
    arr = Array('d', unshared_arr)
    print "Originally, the first two elements of arr = %s"%(arr[:2])

    # Create, start, and finish the child processes
    p = Process(target=f, args=(arr,))
    p.start()
    p.join()

    # Printing out the changed values
    print "Now, the first two elements of arr = %s"%arr[:2]

यह उत्पादन जैसे:

Originally, the first two elements of arr = [0.3518653236697369, 0.517794725524976]
Now, the first two elements of arr = [-0.3518653236697369, 0.517794725524976]

सरणी को ctypes तरीके से एक्सेस किया जा सकता है, उदाहरण के लिए arr[i]समझ में आता है। हालाँकि, यह कोई सुपीरियर एरे नहीं है, और मैं ऑपरेशन जैसे -1*arr, या नहीं कर सकता arr.sum()। मुझे लगता है कि ctypes सरणी को एक सुपीरियर सरणी में बदलने के लिए एक समाधान होगा। हालाँकि (इसके अलावा यह काम करने में सक्षम नहीं है), मुझे नहीं लगता कि इसे अब साझा किया जाएगा।

ऐसा लगता है कि एक सामान्य समस्या के लिए एक मानक समाधान होगा।


1
यह इस एक के रूप में ही नहीं है? stackoverflow.com/questions/5033799/…
19

1
यह एक ही सवाल नहीं है। जुड़ा हुआ प्रश्न इसके subprocessबजाय के बारे में पूछ रहा है multiprocessing
एंड्रयू

जवाबों:


82

@ Unutbu's (अब उपलब्ध नहीं) और @Henry Gomersall के उत्तरों में जोड़ने के लिए। shared_arr.get_lock()जरूरत पड़ने पर आप एक्सेस को सिंक्रनाइज़ करने के लिए उपयोग कर सकते हैं :

shared_arr = mp.Array(ctypes.c_double, N)
# ...
def f(i): # could be anything numpy accepts as an index such another numpy array
    with shared_arr.get_lock(): # synchronize access
        arr = np.frombuffer(shared_arr.get_obj()) # no data copying
        arr[i] = -arr[i]

उदाहरण

import ctypes
import logging
import multiprocessing as mp

from contextlib import closing

import numpy as np

info = mp.get_logger().info

def main():
    logger = mp.log_to_stderr()
    logger.setLevel(logging.INFO)

    # create shared array
    N, M = 100, 11
    shared_arr = mp.Array(ctypes.c_double, N)
    arr = tonumpyarray(shared_arr)

    # fill with random values
    arr[:] = np.random.uniform(size=N)
    arr_orig = arr.copy()

    # write to arr from different processes
    with closing(mp.Pool(initializer=init, initargs=(shared_arr,))) as p:
        # many processes access the same slice
        stop_f = N // 10
        p.map_async(f, [slice(stop_f)]*M)

        # many processes access different slices of the same array
        assert M % 2 # odd
        step = N // 10
        p.map_async(g, [slice(i, i + step) for i in range(stop_f, N, step)])
    p.join()
    assert np.allclose(((-1)**M)*tonumpyarray(shared_arr), arr_orig)

def init(shared_arr_):
    global shared_arr
    shared_arr = shared_arr_ # must be inherited, not passed as an argument

def tonumpyarray(mp_arr):
    return np.frombuffer(mp_arr.get_obj())

def f(i):
    """synchronized."""
    with shared_arr.get_lock(): # synchronize access
        g(i)

def g(i):
    """no synchronization."""
    info("start %s" % (i,))
    arr = tonumpyarray(shared_arr)
    arr[i] = -1 * arr[i]
    info("end   %s" % (i,))

if __name__ == '__main__':
    mp.freeze_support()
    main()

यदि आपको सिंक्रनाइज़ एक्सेस की आवश्यकता नहीं है या आप अपने स्वयं के ताले बनाते हैं तो mp.Array()अनावश्यक है। आप mp.sharedctypes.RawArrayइस मामले में उपयोग कर सकते हैं ।


2
सुंदर जवाब! यदि मैं एक से अधिक साझा सरणी रखना चाहता हूं, तो प्रत्येक अलग-अलग लॉक करने योग्य है, लेकिन रनटाइम पर निर्धारित सरणियों की संख्या के साथ, क्या आपने यहां क्या किया है इसका एक सीधा विस्तार है?
एंड्रयू

3
@ साझा: बच्चे की प्रक्रिया शुरू होने से पहले साझा सरणियाँ बनाई जानी चाहिए ।
शाम

संचालन के आदेश के बारे में अच्छी बात है। हालांकि, मेरे मन में यह था: साझा सरणियों का एक उपयोगकर्ता-निर्दिष्ट संख्या बनाएं, फिर कुछ बच्चे प्रक्रियाएं करें। क्या वह सीधा है?
एंड्रयू

1
@ विधि: आप ऐरे का आकार नहीं बदल सकते। इसे स्मृति के एक साझा ब्लॉक के रूप में सोचें, जिसे बच्चे की प्रक्रिया शुरू होने से पहले आवंटित किया जाना था। आप सभी स्मृति जैसे उपयोग करने की आवश्यकता नहीं है, आप दे सकते हैं countकरने के लिए numpy.frombuffer()। आप इसे उपयोग करने योग्य बनाने के लिए निम्न स्तर पर mmapया ऐसा कुछ करने का प्रयास कर सकते हैं, जैसे posix_ipcकि रेज़िवेबल को लागू करने के लिए सीधे (कॉपी करते समय कॉपी करना शामिल हो सकता है) RawArray एनालॉग (या किसी मौजूदा लाइब्रेरी की तलाश)। या यदि आपका कार्य इसे अनुमति देता है: डेटा को भागों में कॉपी करें (यदि आपको एक बार में सभी की आवश्यकता नहीं है)। "साझा स्मृति का आकार कैसे बदलें" एक अच्छा अलग प्रश्न है।
jfs

1
@umopapisdn: Pool()प्रक्रियाओं की संख्या को परिभाषित करता है (उपलब्ध सीपीयू कोर की संख्या डिफ़ॉल्ट रूप से उपयोग की जाती है)। फ़ंक्शन Mकी संख्या f()को कहा जाता है।
JFS

21

Arrayवस्तु एक है get_obj()विधि इसके साथ जुड़े, जो ctypes सरणी जो एक बफर इंटरफ़ेस प्रस्तुत करता है देता है। मुझे लगता है कि निम्नलिखित काम करना चाहिए ...

from multiprocessing import Process, Array
import scipy
import numpy

def f(a):
    a[0] = -a[0]

if __name__ == '__main__':
    # Create the array
    N = int(10)
    unshared_arr = scipy.rand(N)
    a = Array('d', unshared_arr)
    print "Originally, the first two elements of arr = %s"%(a[:2])

    # Create, start, and finish the child process
    p = Process(target=f, args=(a,))
    p.start()
    p.join()

    # Print out the changed values
    print "Now, the first two elements of arr = %s"%a[:2]

    b = numpy.frombuffer(a.get_obj())

    b[0] = 10.0
    print a[0]

जब चलाते हैं, तो यह पहले तत्व के a10.0 को प्रिंट करता है , दिखा रहा है aऔर bएक ही मेमोरी में सिर्फ दो दृश्य हैं।

यह सुनिश्चित करने के लिए कि यह अभी भी मल्टीप्रोसेसर सुरक्षित है, मेरा मानना ​​है कि आपको ऑब्जेक्ट पर मौजूद विधियों acquireऔर releaseविधियों का उपयोग करना होगा , और इसके बिल्ट इन लॉक को सुनिश्चित करने के लिए कि यह सभी सुरक्षित रूप से एक्सेस किया गया है (हालांकि मैं ऑर्डर पर विशेषज्ञ नहीं हूं मल्टीप्रोसेसर मॉड्यूल)।Arraya


यह सिंक्रनाइज़ेशन के बिना काम नहीं करेगा क्योंकि @unutbu ने अपने (अब हटाए गए) उत्तर में प्रदर्शन किया।
jfs

1
मुमकिन है, अगर आप सिर्फ एरे पोस्ट प्रोसेसिंग को एक्सेस करना चाहते हैं, तो यह कंसीडर मुद्दे और लॉकिंग की चिंता किए बिना सफाई से किया जा सकता है?
हेनरी गोमरसेल

इस मामले में आप की जरूरत नहीं है mp.Array
jfs

1
प्रोसेसिंग कोड में लॉक एरेज़ की आवश्यकता हो सकती है, लेकिन डेटा की पोस्ट प्रोसेसिंग व्याख्या आवश्यक नहीं हो सकती है। मुझे लगता है कि यह समझने से आता है कि वास्तव में समस्या क्या है। स्पष्ट रूप से, साझा किए गए डेटा को समवर्ती रूप से एक्सेस करने के लिए कुछ सुरक्षा की आवश्यकता होती है, जो मुझे लगा कि यह स्पष्ट होगा!
हेनरी गोमरसेल

16

जबकि पहले से दिए गए उत्तर अच्छे हैं, इस समस्या का बहुत आसान समाधान है बशर्ते दो शर्तें पूरी हों:

  1. आप एक POSIX- अनुरूप ऑपरेटिंग सिस्टम (जैसे लिनक्स, मैक OSX) पर हैं; तथा
  2. आपके बच्चे की प्रक्रियाओं को साझा सरणी में केवल पढ़ने के लिए उपयोग की आवश्यकता है।

इस मामले में आपको स्पष्ट रूप से साझा किए जाने वाले चर बनाने की आवश्यकता नहीं है, क्योंकि कांटा का उपयोग करके बच्चे की प्रक्रियाएं बनाई जाएंगी। एक कांटा बच्चा अपने आप माता-पिता की मेमोरी स्पेस को साझा करता है। पायथन मल्टीप्रोसेसिंग के संदर्भ में, इसका मतलब है कि यह सभी मॉड्यूल-स्तरीय चर साझा करता है; ध्यान दें कि यह उन तर्कों के लिए नहीं है जिन्हें आप स्पष्ट रूप से अपने बच्चे की प्रक्रियाओं या उन कार्यों के लिए पास करते हैं जिन्हें आप multiprocessing.Poolया तो कॉल करते हैं ।

एक सरल उदाहरण:

import multiprocessing
import numpy as np

# will hold the (implicitly mem-shared) data
data_array = None

# child worker function
def job_handler(num):
    # built-in id() returns unique memory ID of a variable
    return id(data_array), np.sum(data_array)

def launch_jobs(data, num_jobs=5, num_worker=4):
    global data_array
    data_array = data

    pool = multiprocessing.Pool(num_worker)
    return pool.map(job_handler, range(num_jobs))

# create some random data and execute the child jobs
mem_ids, sumvals = zip(*launch_jobs(np.random.rand(10)))

# this will print 'True' on POSIX OS, since the data was shared
print(np.all(np.asarray(mem_ids) == id(data_array)))

3
+1 वास्तव में मूल्यवान जानकारी। क्या आप बता सकते हैं कि यह केवल मॉड्यूल-स्तरीय संस्करण क्यों हैं जो साझा किए जाते हैं? स्थानीय संस्करण माता-पिता की मेमोरी स्पेस का हिस्सा क्यों नहीं हैं? उदाहरण के लिए, यदि मेरे पास एक कार्य F है, तो स्थानीय var V के साथ और G के F के अंदर एक फ़ंक्शन G क्यों है, जो V का संदर्भ नहीं देता है?
Coffee_Table

5
चेतावनी: यह उत्तर थोड़ा भ्रामक है। बाल प्रक्रिया कांटा के समय, वैश्विक चर सहित, मूल प्रक्रिया की एक प्रति प्राप्त करती है। राज्य किसी भी तरह से सिंक्रनाइज़ नहीं हैं और उस क्षण से विचलन करेंगे। यह तकनीक कुछ परिदृश्यों में उपयोगी हो सकती है (उदाहरण के लिए: एड-हॉक चाइल्ड प्रक्रियाओं को बंद करना जो प्रत्येक माता-पिता की प्रक्रिया का एक स्नैपशॉट संभालती हैं और फिर समाप्त हो जाती हैं), लेकिन दूसरों में बेकार है (जैसे: लंबे समय तक चलने वाली बच्चे की प्रक्रियाएं जिन्हें साझा करना है और मूल प्रक्रिया के साथ सिंक डेटा)।
डेविड स्टीन

4
@EelkeSpaak: आपका कथन - "एक कांटा लगा हुआ बच्चा अपने आप माता-पिता की मेमोरी स्पेस को साझा करता है" - गलत है। अगर मेरे पास एक बच्चा प्रक्रिया है जो माता-पिता की प्रक्रिया की स्थिति की निगरानी करना चाहती है, तो कड़ाई से केवल-पढ़ने के तरीके में, मुझे वहां नहीं मिलेगा: बच्चा केवल पलटने के समय माता-पिता की स्थिति का एक स्नैपशॉट देखता है। वास्तव में, यह ठीक है कि मैं क्या कर रहा था (आपके उत्तर के बाद) जब मैंने इस सीमा की खोज की। इसलिए आपके उत्तर पर पोस्टस्क्रिप्ट। संक्षेप में: मूल स्थिति "साझा नहीं" है, लेकिन केवल बच्चे को कॉपी किया गया है। यह सामान्य अर्थों में "साझाकरण" नहीं है।
डेविड स्टीन

2
क्या मुझे लगता है कि यह एक कॉपी-ऑन-राइट स्थिति है, कम से कम पॉज़िक्स सिस्टम पर। यही है, कांटे के बाद, मुझे लगता है कि नया डेटा लिखे जाने तक मेमोरी साझा की जाती है, जिस बिंदु पर एक प्रतिलिपि बनाई जाती है। तो हाँ, यह सच है कि डेटा बिल्कुल "साझा" नहीं है, लेकिन यह संभावित रूप से बहुत बड़ा प्रदर्शन को बढ़ावा दे सकता है। यदि आपकी प्रक्रिया को केवल पढ़ा जाता है, तो कोई भी नकल उपरि नहीं होगी! क्या मैंने बात को सही तरीके से समझा है?
प्रेषक ३१'१

2
@senderle हां, मेरा वास्तव में यही मतलब है! इसलिए मेरी बात (2) केवल पढ़ने के लिए उपयोग के बारे में जवाब में।
ईलकेस्पक

11

मैंने एक छोटा अजगर मॉड्यूल लिखा है जो कि अजगर व्याख्याकारों के बीच सुन्न सरणियों को साझा करने के लिए POSIX साझा मेमोरी का उपयोग करता है। शायद आपको यह आसान लगे।

https://pypi.python.org/pypi/SharedArray

यहां देखिए यह कैसे काम करता है:

import numpy as np
import SharedArray as sa

# Create an array in shared memory
a = sa.create("test1", 10)

# Attach it as a different array. This can be done from another
# python interpreter as long as it runs on the same computer.
b = sa.attach("test1")

# See how they are actually sharing the same memory block
a[0] = 42
print(b[0])

# Destroying a does not affect b.
del a
print(b[0])

# See how "test1" is still present in shared memory even though we
# destroyed the array a.
sa.list()

# Now destroy the array "test1" from memory.
sa.delete("test1")

# The array b is not affected, but once you destroy it then the
# data are lost.
print(b[0])

8

आप sharedmemमॉड्यूल का उपयोग कर सकते हैं : https://bitbucket.org/cleemesser/numpy-sodedmem

यहां आपका मूल कोड तब, इस बार साझा मेमोरी का उपयोग करके किया गया है जो एक NumPy सरणी की तरह व्यवहार करता है (अतिरिक्त अंतिम विवरण को एक NumPy sum()फ़ंक्शन कहते हैं):

from multiprocessing import Process
import sharedmem
import scipy

def f(a):
    a[0] = -a[0]

if __name__ == '__main__':
    # Create the array
    N = int(10)
    unshared_arr = scipy.rand(N)
    arr = sharedmem.empty(N)
    arr[:] = unshared_arr.copy()
    print "Originally, the first two elements of arr = %s"%(arr[:2])

    # Create, start, and finish the child process
    p = Process(target=f, args=(arr,))
    p.start()
    p.join()

    # Print out the changed values
    print "Now, the first two elements of arr = %s"%arr[:2]

    # Perform some NumPy operation
    print arr.sum()

1
नोट: इस नहीं रह गया है विकसित किया जा रहा है और पर linux काम करने के लिए प्रतीत नहीं होता github.com/sturlamolden/sharedmem-numpy/issues/4

numpy-sharedmem विकास में नहीं हो सकता है, लेकिन यह अभी भी लिनक्स पर काम करता है, github.com/vmlaker/benchmark-saredmem देखें
वेलिमेर म्लेकर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.