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


111

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

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]

অ্যারেটি একটি টাইপ পদ্ধতিতে অ্যাক্সেস করা যায়, উদাহরণস্বরূপ arr[i]বোধ করে। তবে এটি কোনও নম্র অ্যারে নয় এবং আমি -1*arr, বা এর মতো অপারেশন করতে পারি না arr.sum()। আমি মনে করি যে একটি সমাধান হ'ল সিটিপস অ্যারেটিকে একটি আঙ্কুল অ্যারেতে রূপান্তর করা। তবে (এই কাজটি করতে সক্ষম না হওয়া), আমি বিশ্বাস করি না এটি আর ভাগ করা হবে।

এটি সাধারণ সমস্যা হতে পারে তার একটি স্ট্যান্ডার্ড সমাধান হবে বলে মনে হয়।


1
এটি কি এই মত এক না? stackoverflow.com/questions/5033799/...
pygabriel

1
এটি বেশ একই প্রশ্ন নয়। লিঙ্কযুক্ত প্রশ্নটি subprocessবরং জিজ্ঞাসা করছে multiprocessing
অ্যান্ড্রু

উত্তর:


82

@ আনটবু'র (আর উপলভ্য নয়) এবং @ হেনরি গোমারসাল এর উত্তরগুলিতে যুক্ত করতে। আপনি 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
@ অ্যান্ড্রু: শিশু প্রক্রিয়া তৈরি হওয়ার আগে ভাগ করে নেওয়া অ্যারে তৈরি করা উচিত ।
jfs

অপারেশন ক্রম সম্পর্কে ভাল পয়েন্ট। যদিও এটি আমার মনে ছিল: একটি ব্যবহারকারী-নির্দিষ্ট সংখ্যক ভাগ করে নেওয়া অ্যারে তৈরি করুন, তারপরে কয়েকটি শিশু প্রক্রিয়া তৈরি করুন। এটা কি সোজা?
অ্যান্ড্রু

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]

যখন চালানোর জন্য, প্রথম উপাদান আউট এই কপি করে প্রিন্ট aএখন 10.0 হচ্ছে, দেখাচ্ছে aএবং bএকই মেমরিতে মাত্র দুই দৃশ্য।

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


@ ইউনতবু তার (এখন মুছে ফেলা) উত্তরে প্রদর্শিত হিসাবে এটি সিঙ্ক্রোনাইজেশন ছাড়া কাজ করবে না।
jfs

1
সম্ভবতঃ আপনি যদি অ্যারে পোস্ট প্রসেসিংটি অ্যাক্সেস করতে চান তবে এটি সম্মতি সংক্রান্ত সমস্যা এবং লকিং নিয়ে চিন্তা না করে পরিষ্কারভাবে করা যেতে পারে?
হেনরি গোমারসাল

এই ক্ষেত্রে আপনার প্রয়োজন হবে না mp.Array
jfs

1
প্রসেসিং কোডে লক করা অ্যারেগুলির প্রয়োজন হতে পারে, তবে ডেটার পোস্ট প্রসেসিং ব্যাখ্যার প্রয়োজন হয় না। আমার ধারণা এই সমস্যাটি আসলে কী তা বোঝার পরে আসে। স্পষ্টতই, একই সাথে ভাগ করা ডেটা অ্যাক্সেস করতে কিছু সুরক্ষা প্রয়োজন যা আমি ভেবেছিলাম যে তা সুস্পষ্ট হবে!
হেনরি গোমারসাল

16

ইতিমধ্যে প্রদত্ত উত্তরগুলি ভাল হলেও দুটি শর্ত পূরণ হলে এই সমস্যার খুব সহজ সমাধান পাওয়া যায়:

  1. আপনি একটি পসিএক্স-কমপ্লায়েন্ট অপারেটিং সিস্টেমে রয়েছেন (যেমন লিনাক্স, ম্যাক ওএসএক্স); এবং
  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 সত্যই মূল্যবান তথ্য। আপনি কেন এটি ব্যাখ্যা করতে পারেন যে এটি কেন কেবলমাত্র মডিউল-স্তরের ভার্সে ভাগ করা হয়? স্থানীয় যুদ্ধগুলি কেন পিতামাতার স্মৃতির জায়গার অংশ নয়? উদাহরণস্বরূপ, আমার যদি স্থানীয় ভের ভি সহ একটি ফাংশন থাকে এবং এফ এর ভিতরে একটি ফাংশন জি থাকে যা ভি'র উল্লেখ করে?
কফি_টেবল

5
সতর্কতা: এই উত্তরটি একটু প্রতারণামূলক। কাঁটাচামানের সময় শিশু প্রক্রিয়া গ্লোবাল ভেরিয়েবল সহ পিতামাতার প্রক্রিয়াটির অবস্থার অনুলিপি গ্রহণ করে। রাজ্যগুলি কোনওভাবেই সিঙ্ক্রোনাইজড নয় এবং সেই মুহুর্ত থেকে বিচ্ছিন্ন হবে। এই কৌশলটি কিছু পরিস্থিতিতে কার্যকর হতে পারে (যেমন: প্রতিটি পিতামাতার প্রক্রিয়াটির একটি স্ন্যাপশট পরিচালনা করে এবং তারপরে সমাপ্ত হয়) তবে অন্যদের মধ্যে এটি অকেজো (যেমন: দীর্ঘকাল ধরে চলমান শিশু প্রক্রিয়াগুলি যা ভাগ করে নিতে হয় এবং পিতামাতার প্রক্রিয়াটির সাথে ডেটা সিঙ্ক করুন)।
ডেভিড স্টেইন

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

2
কমপক্ষে পোস্টিক্স সিস্টেমে এটি অনুলিপি করার পরিস্থিতি বলে আমি কী ভুল করে বসেছি? এটি, কাঁটাচামচ করার পরে, আমি মনে করি নতুন ডেটা না লেখা পর্যন্ত মেমরিটি ভাগ করা হয়, যার বিন্দুতে একটি অনুলিপি তৈরি করা হয়। সুতরাং হ্যাঁ, এটি সত্য যে ডেটাটি ঠিক "ভাগ" করা হয়নি, তবে এটি সম্ভাব্য বিশাল পারফরম্যান্সের উত্সাহ সরবরাহ করতে পারে। যদি আপনার প্রক্রিয়াটি কেবল পঠিত হয়, তবে কোনও অনুলিপি ওভারহেড থাকবে না! আমি কি বিষয়টি সঠিকভাবে বুঝতে পেরেছি?
প্রেরক

2
@ সেন্ডারেল হ্যাঁ, এটাই আমার অর্থ! সুতরাং পঠনযোগ্য অ্যাক্সেস সম্পর্কে উত্তরের আমার পয়েন্ট (2)।
EelkeSpaak

11

আমি একটি ছোট অজগর মডিউল লিখেছি যা পাইসন শেয়ার্ড মেমরি ব্যবহার করে পাইথন দোভাষীদের মধ্যে নপি অ্যারে ভাগ করে নিতে। সম্ভবত আপনি এটি সুবিধাজনক খুঁজে পাবেন।

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-sharedmem

তারপরে আপনার আসল কোডটি এইবার, শেয়ার্ড মেমোরি ব্যবহার করে যা নম্পপি অ্যারের মতো আচরণ করে (অতিরিক্ত সর্বশেষ বিবৃতিটি নুমপি 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
দ্রষ্টব্য: এটি আর বিকশিত হচ্ছে না এবং লিনাক্স github.com/sturlamolden/sharedmem-numpy/issues/4
এডি

নম্পি-শেয়ারডেমের বিকাশ নাও হতে পারে তবে এটি লিনাক্সে এখনও কাজ করে, github.com/vmlaker/benchmark-sharedmem দেখুন
ভেলিমির ম্লেকার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.