মাল্টিপ্রসেসিংয়ে ভাগ করা মেমরি অবজেক্ট


123

ধরা যাক আমার কাছে মেমরি ন্যাম্পি অ্যারে বড় রয়েছে, আমার একটি ফাংশন রয়েছে funcযা এই জায়ান্ট অ্যারেটিকে ইনপুট হিসাবে গ্রহণ করে (কিছু অন্যান্য প্যারামিটারের সাথে একসাথে)। funcবিভিন্ন পরামিতি সমান্তরালে চালানো যেতে পারে। উদাহরণ স্বরূপ:

def func(arr, param):
    # do stuff to arr, param

# build array arr

pool = Pool(processes = 6)
results = [pool.apply_async(func, [arr, param]) for param in all_params]
output = [res.get() for res in results]

যদি আমি মাল্টিপ্রসেসিং লাইব্রেরি ব্যবহার করি তবে সেই দৈত্য অ্যারেটি একাধিকবার বিভিন্ন প্রসেসে অনুলিপি করা হবে।

বিভিন্ন প্রক্রিয়াগুলিকে একই অ্যারে ভাগ করার কোনও উপায় আছে কি? এই অ্যারে অবজেক্টটি কেবল পঠনযোগ্য এবং কখনই পরিবর্তিত হবে না।

আর কী জটিল, যদি অ্যারেটি অ্যারে না হয় তবে একটি স্বেচ্ছাচারিত পাইথন বস্তু, এটিকে ভাগ করার কোনও উপায় আছে কি?

[সম্পাদনা]

আমি উত্তরটি পড়েছি তবে আমি এখনও কিছুটা বিভ্রান্ত। যেহেতু কাঁটাচামচ (অনুলিপি) অনুলিপি, তাই পাইথন মাল্টিপ্রসেসিং লাইব্রেরিতে নতুন প্রক্রিয়া তৈরি করার সময় আমাদের কোনও অতিরিক্ত ব্যয় করা উচিত নয়। তবে নীচের কোডটি দেখায় যে এখানে একটি বিশাল ওভারহেড রয়েছে:

from multiprocessing import Pool, Manager
import numpy as np; 
import time

def f(arr):
    return len(arr)

t = time.time()
arr = np.arange(10000000)
print "construct array = ", time.time() - t;


pool = Pool(processes = 6)

t = time.time()
res = pool.apply_async(f, [arr,])
res.get()
print "multiprocessing overhead = ", time.time() - t;

আউটপুট (এবং যাইহোক, অ্যারের আকার বাড়ার সাথে সাথে ব্যয় বৃদ্ধি পায়, তাই আমি সন্দেহ করি যে মেমরি অনুলিপি সম্পর্কিত এখনও ওভারহেড রয়েছে):

construct array =  0.0178790092468
multiprocessing overhead =  0.252444982529

এত বড় ওভারহেড কেন আছে, যদি আমরা অ্যারেটি অনুলিপি না করি? এবং ভাগ করা মেমরিটি কোন অংশটি আমাকে বাঁচায়?



আপনি ডক্সকে দেখেছেন , তাই না?
লেভ লেভিটস্কি

@ ফ্রেঞ্চিস অ্যাভিলা কি কেবল অ্যারে নয়, স্বেচ্ছাচারিত পাইথন বস্তু ভাগ করার উপায় আছে?
ভেন্ডেটা

1
@ লাইভলভিটস্কি আমাকে জিজ্ঞাসা করতে হবে, কেবল অ্যারে নয়, স্বেচ্ছাচারিত পাইথন বস্তুগুলি ভাগ করার কোনও উপায় আছে কি?
ভেন্ডেটা

2
এই উত্তরটি সুন্দরভাবে ব্যাখ্যা করে যে কেন নির্বিচারে পাইথন অবজেক্টগুলি ভাগ করা যায় না।
জান্নে কারিলা 23'12

উত্তর:


121

আপনি যদি এমন কোনও অপারেটিং সিস্টেম ব্যবহার করেন যা কপিরাইট-অন- fork()রাইটিং শব্দার্থবিজ্ঞান (যে কোনও সাধারণ ইউনিক্সের মতো) ব্যবহার করে, তবে যতক্ষণ না আপনি আপনার ডেটা কাঠামোটি কখনও পরিবর্তন করেন না এটি অতিরিক্ত স্মৃতি গ্রহণ না করে সমস্ত শিশু প্রসেসের জন্য উপলব্ধ থাকবে। আপনাকে বিশেষ কিছু করতে হবে না (কেবলমাত্র নিশ্চিত হয়ে নিন যে আপনি বস্তুটি পরিবর্তন করেন না)।

আপনার সমস্যার জন্য আপনি সবচেয়ে কার্যকরী জিনিসটি হ'ল আপনার অ্যারেটিকে একটি দক্ষ অ্যারে কাঠামোতে (ব্যবহার করে numpyবা array) প্যাক করা , ভাগ করে নেওয়া মেমরিটিতে রাখুন multiprocessing.Array, এটিকে আবদ্ধ করুন এবং এটি আপনার কার্যক্রমে প্রেরণ করুন। এই উত্তরটি দেখায় যে এটি কীভাবে করা যায়

আপনি যদি কোনও লিখনযোগ্য শেয়ার্ড অবজেক্ট চান, তবে আপনাকে এটি কোনও ধরণের সিঙ্ক্রোনাইজেশন বা লকিংয়ের সাথে আবৃত করতে হবে। multiprocessingএটি করার দুটি পদ্ধতি সরবরাহ করে: একটি ভাগ করা মেমরি ব্যবহার করে (সাধারণ মান, অ্যারে, বা সিটিপস জন্য উপযুক্ত) বা একটি Managerপ্রক্সি, যেখানে একটি প্রক্রিয়া মেমরি ধারণ করে এবং কোনও ব্যবস্থাপক অন্যান্য প্রক্রিয়াগুলি (এমনকি কোনও নেটওয়ার্কের বাইরেও) এটিকে অ্যাক্সেস আর্বিট্রেট করে।

Managerপদ্ধতির নির্বিচারে পাইথন বস্তুর সঙ্গে ব্যবহার করা যেতে পারে, কিন্তু সমতুল্য ব্যবহার ভাগ করা মেমোরি তুলনায় ধীর হতে হবে কারণ বস্তু ধারাবাহিকভাবে করা / deserialized ও প্রক্রিয়া মধ্যে পাঠানো প্রয়োজন।

একটি আছে সমান্তরাল প্রক্রিয়াজাতকরণ লাইব্রেরি সম্পদ ও পাইথন পাওয়া পন্থাmultiprocessingএটি একটি দুর্দান্ত এবং বৃত্তাকার গ্রন্থাগার, তবে আপনার যদি বিশেষ চাহিদা থাকে তবে অন্য পদ্ধতির একটি হতে পারে আরও ভাল।


25
শুধু লক্ষণীয়, পাইথন কাঁটাচামচ () এর অর্থ আসলে অ্যাক্সেসের অনুলিপি (কারণ কেবলমাত্র অ্যাক্সেসটি অ্যাক্সেস করার ফলে তার রেফ-কাউন্ট পরিবর্তন হবে)।
ফ্যাবিও জাদরোজনি

3
@ ফ্যাবিজ্যাডরোজনি এটি কি পুরো বস্তুটি অনুলিপি করতে পারে, বা কেবলমাত্র মেমোরি পৃষ্ঠাটিতে তার পুনঃসমাংশ রয়েছে?
জিগ জিগ

5
আফাইক, কেবল রিম্যাকউন্ট সহ মেমরি পৃষ্ঠায় (সুতরাং, প্রতিটি বস্তুর অ্যাক্সেসের 4kb) থাকে।
ফ্যাবিও জাদরোজনি

1
@ ম্যাক্স একটি বন্ধ ব্যবহার করুন। প্রদত্ত ফাংশনটি apply_asyncতার যুক্তিগুলির পরিবর্তে ভাগ করে নেওয়া অবজেক্টকে সরাসরি স্কোপে রেফারেন্স করা উচিত।
ফ্রান্সিস অবিলা

3
@ ফ্রানসিস অ্যাভিলা আপনি কীভাবে বন্ধ ব্যবহার করবেন? আপনি প্রয়োগ_সায়েন্সে যে কার্যটি দেন তা কি পছন্দনীয় হওয়া উচিত নয়? বা এটি কি কেবল একটি মানচিত্র_চ্যামিতির সীমাবদ্ধতা?
জার্মানি

17

আমি একই সমস্যার মধ্যে চলে এসেছি এবং এর চারপাশে কাজ করার জন্য একটি সামান্য শেয়ার্ড-মেমরি ইউটিলিটি ক্লাস লিখেছি।

আমি multiprocessing.RawArray(লকফ্রি) ব্যবহার করছি , এবং অ্যারেগুলিতে অ্যাক্সেসটি মোটেও সিঙ্ক্রোনাইজ করা হয়নি (লকফ্রি), নিজের পা গুলি না করার বিষয়ে সতর্ক থাকুন।

সমাধানের সাথে আমি কোয়াড-কোর আই 7 তে প্রায় 3 এর একটি ফ্যাক্টর দ্বারা স্পিডআপগুলি পাই।

এখানে কোডটি রয়েছে: এটি ব্যবহার এবং উন্নত করতে নির্দ্বিধায় এবং কোনও বাগের পিছনে রিপোর্ট করুন back

'''
Created on 14.05.2013

@author: martin
'''

import multiprocessing
import ctypes
import numpy as np

class SharedNumpyMemManagerError(Exception):
    pass

'''
Singleton Pattern
'''
class SharedNumpyMemManager:    

    _initSize = 1024

    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SharedNumpyMemManager, cls).__new__(
                                cls, *args, **kwargs)
        return cls._instance        

    def __init__(self):
        self.lock = multiprocessing.Lock()
        self.cur = 0
        self.cnt = 0
        self.shared_arrays = [None] * SharedNumpyMemManager._initSize

    def __createArray(self, dimensions, ctype=ctypes.c_double):

        self.lock.acquire()

        # double size if necessary
        if (self.cnt >= len(self.shared_arrays)):
            self.shared_arrays = self.shared_arrays + [None] * len(self.shared_arrays)

        # next handle
        self.__getNextFreeHdl()        

        # create array in shared memory segment
        shared_array_base = multiprocessing.RawArray(ctype, np.prod(dimensions))

        # convert to numpy array vie ctypeslib
        self.shared_arrays[self.cur] = np.ctypeslib.as_array(shared_array_base)

        # do a reshape for correct dimensions            
        # Returns a masked array containing the same data, but with a new shape.
        # The result is a view on the original array
        self.shared_arrays[self.cur] = self.shared_arrays[self.cnt].reshape(dimensions)

        # update cnt
        self.cnt += 1

        self.lock.release()

        # return handle to the shared memory numpy array
        return self.cur

    def __getNextFreeHdl(self):
        orgCur = self.cur
        while self.shared_arrays[self.cur] is not None:
            self.cur = (self.cur + 1) % len(self.shared_arrays)
            if orgCur == self.cur:
                raise SharedNumpyMemManagerError('Max Number of Shared Numpy Arrays Exceeded!')

    def __freeArray(self, hdl):
        self.lock.acquire()
        # set reference to None
        if self.shared_arrays[hdl] is not None: # consider multiple calls to free
            self.shared_arrays[hdl] = None
            self.cnt -= 1
        self.lock.release()

    def __getArray(self, i):
        return self.shared_arrays[i]

    @staticmethod
    def getInstance():
        if not SharedNumpyMemManager._instance:
            SharedNumpyMemManager._instance = SharedNumpyMemManager()
        return SharedNumpyMemManager._instance

    @staticmethod
    def createArray(*args, **kwargs):
        return SharedNumpyMemManager.getInstance().__createArray(*args, **kwargs)

    @staticmethod
    def getArray(*args, **kwargs):
        return SharedNumpyMemManager.getInstance().__getArray(*args, **kwargs)

    @staticmethod    
    def freeArray(*args, **kwargs):
        return SharedNumpyMemManager.getInstance().__freeArray(*args, **kwargs)

# Init Singleton on module load
SharedNumpyMemManager.getInstance()

if __name__ == '__main__':

    import timeit

    N_PROC = 8
    INNER_LOOP = 10000
    N = 1000

    def propagate(t):
        i, shm_hdl, evidence = t
        a = SharedNumpyMemManager.getArray(shm_hdl)
        for j in range(INNER_LOOP):
            a[i] = i

    class Parallel_Dummy_PF:

        def __init__(self, N):
            self.N = N
            self.arrayHdl = SharedNumpyMemManager.createArray(self.N, ctype=ctypes.c_double)            
            self.pool = multiprocessing.Pool(processes=N_PROC)

        def update_par(self, evidence):
            self.pool.map(propagate, zip(range(self.N), [self.arrayHdl] * self.N, [evidence] * self.N))

        def update_seq(self, evidence):
            for i in range(self.N):
                propagate((i, self.arrayHdl, evidence))

        def getArray(self):
            return SharedNumpyMemManager.getArray(self.arrayHdl)

    def parallelExec():
        pf = Parallel_Dummy_PF(N)
        print(pf.getArray())
        pf.update_par(5)
        print(pf.getArray())

    def sequentialExec():
        pf = Parallel_Dummy_PF(N)
        print(pf.getArray())
        pf.update_seq(5)
        print(pf.getArray())

    t1 = timeit.Timer("sequentialExec()", "from __main__ import sequentialExec")
    t2 = timeit.Timer("parallelExec()", "from __main__ import parallelExec")

    print("Sequential: ", t1.timeit(number=1))    
    print("Parallel: ", t2.timeit(number=1))

কেবল উপলব্ধি হয়ে গেছে যে আপনি মাল্টিপ্রসেসিং পুল তৈরির আগে আপনার ভাগ করা মেমরি অ্যারেগুলি সেট আপ করতে হবে, এখনও কেন তা জানেন না তবে এটি অন্যভাবে কাজ করবে না।
martin.preinfalk

পুলটি তাত্ক্ষণিকভাবে বন্ধ করার সময় মাল্টিপ্রসেসিং পুলটি কাঁটাচামচ কল করে () কারণ, এর পরে যে কোনও কিছুই পরে তৈরি করা কোনও ভাগ করা মেমের পয়েন্টারে অ্যাক্সেস পাবে না।
Xiv

আমি যখন পাই কোড 35 এর অধীনে এই কোডটি চেষ্টা করেছি তখন আমি মাল্টিপ্রসেসিং.শ্রেডেক্টটাইপস.পি-তে ব্যতিক্রম পেয়েছি, সুতরাং আমার ধারণা এই কোডটি কেবল পাই 2 এর জন্য।
ডাঃ হিলিয়ের ডানিয়েল

11

এটি রায়ের উদ্দেশ্যে ব্যবহারযোগ্য কেস যা সমান্তরাল এবং বিতরণ পাইথনের পাঠাগার। হুডের নীচে, এটি অ্যাপাচি অ্যারো ডেটা বিন্যাস (যা একটি শূন্য-অনুলিপি বিন্যাস) ব্যবহার করে অবজেক্টগুলিকে সিরিয়ালাইজ করে এবং এগুলিকে একটি ভাগ করে নেওয়া মেমরি অবজেক্ট স্টোরে সংরক্ষণ করে যাতে তারা অনুলিপি তৈরি না করে একাধিক প্রক্রিয়া দ্বারা অ্যাক্সেস করতে পারে।

কোডটি নীচের মত দেখাচ্ছে।

import numpy as np
import ray

ray.init()

@ray.remote
def func(array, param):
    # Do stuff.
    return 1

array = np.ones(10**6)
# Store the array in the shared memory object store once
# so it is not copied multiple times.
array_id = ray.put(array)

result_ids = [func.remote(array_id, i) for i in range(4)]
output = ray.get(result_ids)

যদি আপনি কল না করেন ray.putতবে অ্যারেটি এখনও ভাগ করা মেমরিতে সংরক্ষণ করা হবে, তবে এটি অনুরোধ অনুসারে একবার হয়ে যাবে func, যা আপনি চান তা নয়।

নোট করুন যে এটি কেবল অ্যারেগুলিতেই নয় কেবল অ্যারে যুক্ত বস্তুর জন্যও কাজ করবে , উদাহরণস্বরূপ, অভিধানগুলি ম্যাপিংয়ের জন্য নীচে হিসাবে অ্যারেগুলিতে ম্যাপিং রয়েছে।

আপনি আইপিথনে নিম্নলিখিতটি চালিয়ে রে বনাম আচারে সিরিয়ালাইজেশনের পারফরম্যান্স তুলনা করতে পারেন।

import numpy as np
import pickle
import ray

ray.init()

x = {i: np.ones(10**7) for i in range(20)}

# Time Ray.
%time x_id = ray.put(x)  # 2.4s
%time new_x = ray.get(x_id)  # 0.00073s

# Time pickle.
%time serialized = pickle.dumps(x)  # 2.6s
%time deserialized = pickle.loads(serialized)  # 1.9s

রায়ের সাথে সিরিয়ালকরণ আচারের চেয়ে সামান্য দ্রুত, তবে ভাগ করা মেমরির ব্যবহারের কারণে ডেসরিয়ালাইজেশন 1000x দ্রুত হয় (এই সংখ্যাটি অবশ্যই বস্তুর উপর নির্ভর করবে)।

দেখুন সত্যজিৎ ডকুমেন্টেশন । আপনি রে এবং তীর ব্যবহার করে দ্রুত সিরিয়ালাইজেশন সম্পর্কে আরও পড়তে পারেন । দ্রষ্টব্য আমি রে বিকাশকারীদের মধ্যে একজন।


1
ভাল লাগছে রে! তবে, আমি এই লাইব্রেরিটি আগে ব্যবহার করার চেষ্টা করেছি, তবে দুর্ভাগ্যক্রমে, আমি কেবল বুঝতে পেরেছি যে রায় উইন্ডোজ সমর্থন করে না। আমি আশা করি আপনি ছেলেরা উইন্ডোজ ASAP সমর্থন করতে পারেন। আপনাকে ধন্যবাদ, বিকাশকারীরা!
Hzzkygcs

5

রবার্ট নিশিহার যেমন উল্লেখ করেছেন, অ্যাপাচি অ্যারো এটি সহজ করে তোলে, বিশেষত প্লাজমা ইন মেমোরি অবজেক্ট স্টোরের সাহায্যে, যা রে নির্মিত হয়েছিল।

আমি বিশেষত এই কারণেই মস্তিষ্ক-প্লাজমা তৈরি করেছি - ফ্লাস্ক অ্যাপ্লিকেশনটিতে বড় বস্তুর দ্রুত লোডিং এবং পুনরায় লোড। এটি অ্যাপাচি অ্যারো-সিরিয়ালাইজযোগ্য অবজেক্টগুলির জন্য একটি ভাগ করা-মেমরি অবজেক্ট নেমস্পেস, এর pickleদ্বারা উত্পন্ন 'ডি বাইটস্ট্রিংস সহ pickle.dumps(...)

অ্যাপাচি রে এবং প্লাজমার সাথে মূল পার্থক্য হ'ল এটি আপনার জন্য অবজেক্ট আইডির ট্র্যাক রাখে। স্থানীয়ভাবে চলমান যে কোনও প্রক্রিয়া বা থ্রেড বা প্রোগ্রামগুলি কোনও Brainবস্তু থেকে নাম কল করে ভেরিয়েবলের মানগুলি ভাগ করতে পারে ।

$ pip install brain-plasma
$ plasma_store -m 10000000 -s /tmp/plasma

from brain_plasma import Brain
brain = Brain(path='/tmp/plasma/)

brain['a'] = [1]*10000

brain['a']
# >>> [1,1,1,1,...]
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.