Share Large, Read-Only Numpy Array Between Multiprocessing Processes


90

60GB SciPy Dizim (Matrix) var 5+ multiprocessing Processnesne arasında paylaşmam gerekiyor . Numpy-sharedmem gördüm ve bu tartışmayı SciPy listesinde okudum . İki yaklaşım var gibi görünüyor - numpy-sharedmemve a kullanmak multiprocessing.RawArray()ve NumPy s’yi dtypes’ye eşlemek ctype. Şimdi, numpy-sharedmemgidilecek yol gibi görünüyor, ancak henüz iyi bir referans örneği görmedim. Dizi (aslında bir matris) salt okunur olacağı için herhangi bir kilide ihtiyacım yok. Şimdi, boyutu nedeniyle bir kopyasından kaçınmak istiyorum. Bu gibi sesler doğru yöntem oluşturmaktır sadece a kadar dizinin kopyasharedmem array, and then pass it to the Process objects? A couple of specific questions:

  1. Sharedmem tanıtıcılarını altlara aktarmanın en iyi yolu nedir Process()? Sadece bir diziyi dolaştırmak için kuyruğa ihtiyacım var mı? Bir boru daha mı iyi olur? Bunu Process()alt sınıfın başlangıcına bir argüman olarak iletebilir miyim (burada turşu olduğunu varsayıyorum)?

  2. Yukarıda bağlantılandırdığım tartışmada numpy-sharedmem64bit güvenli olmadığından bahsediliyor mu? Kesinlikle 32 bit adreslenebilir olmayan bazı yapılar kullanıyorum.

  3. RawArray()Yaklaşımda değiş tokuş var mı? Daha yavaş mı, böcek mi?

  4. Numpy-sharedmem yöntemi için herhangi bir ctype-dtype eşlemesine ihtiyacım var mı?

  5. Bunu yapan bir Açık Kaynak kod örneği olan var mı? Ben çok pratik öğrenmiş biriyim ve bakılacak iyi bir örnek olmadan bunu çalıştırmak zor.

Bunu başkalarına açıklığa kavuşturmak için sağlayabileceğim ek bilgiler varsa, lütfen yorum yapın, ben de ekleyeceğim. Teşekkürler!

Bunun Ubuntu Linux ve Belki Mac OS üzerinde çalışması gerekiyor , ancak taşınabilirlik çok büyük bir sorun değil.


1
Farklı süreçler bu diziye yazacaksa, multiprocessingher işlem için her şeyin bir kopyasını almayı bekleyin.
tiago

3
@tiago: "Dizi (aslında bir matris) salt okunur olacağı için herhangi bir kilide ihtiyacım yok"
Dr. Jan-Philip Gehrcke

1
@tiago: ayrıca, çoklu işlem, açıkça belirtilmediği sürece bir kopya oluşturmuyor (argümanlar aracılığıyla target_function). İşletim sistemi, yalnızca değişiklik yapıldıktan sonra ebeveynin belleğinin bölümlerini çocuğun bellek alanına kopyalayacaktır.
Dr Jan-Philip Gehrcke


Daha önce bununla ilgili birkaç soru sordum . Çözümüm burada bulunabilir: github.com/david-hoffman/peaks/blob/… (üzgünüm kod bir felakettir).
David Hoffman

Yanıtlar:


30

@Velimir Mlaker harika bir cevap verdi. Biraz yorum ve küçük bir örnek ekleyebileceğimi düşündüm.

(Sharedmem'de çok fazla belge bulamadım - bunlar kendi deneylerimin sonuçları.)

  1. Alt işlem başlarken veya başladıktan sonra tutamaçları geçmeniz gerekiyor mu? Yalnızca eski ise, targetve argsargümanlarını kullanabilirsiniz.Process . Bu potansiyel olarak global bir değişken kullanmaktan daha iyidir.
  2. Bağlandığınız tartışma sayfasından, 64 bit Linux desteğinin bir süre önce paylaşımlımem'e eklendiği, bu nedenle sorun olmayabileceği anlaşılıyor.
  3. Bunu bilmiyorum.
  4. Hayır. Aşağıdaki örneğe bakın.

Misal

#!/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)

Çıktı

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

Bu ilgili soru faydalı olabilir.


37

Linux'taysanız (veya POSIX uyumlu herhangi bir sistemdeyseniz), bu diziyi genel bir değişken olarak tanımlayabilirsiniz. yeni bir alt süreç başlatırken Linux'ta multiprocessingkullanıyor fork(). Yeni ortaya çıkan bir alt süreç, değiştirmediği sürece hafızayı otomatik olarak ebeveyniyle paylaşır ( yazma üzerine kopyalama mekanizması).

"Herhangi bir kilide ihtiyacım yok, çünkü dizi (aslında bir matris) salt okunur olacaktır" dediğiniz için, bu davranıştan yararlanmak çok basit ve son derece verimli bir yaklaşım olacaktır: tüm çocuk süreçler erişecektir Bu büyük uyuşuk diziyi okurken fiziksel bellekteki aynı veriler.

İçin dizinizi el etmeyin Process()yapıcı, bu talimat verir multiprocessingetmek picklesizin durumunuzda son derece verimsiz veya imkansız olurdu çocuğa verilere. Linux'ta, fork()çocuktan hemen sonra aynı fiziksel belleği kullanan ebeveynin tam bir kopyası vardır, bu nedenle yapmanız gereken tek şey, matrisi 'içeren' Python değişkeninin target, teslim ettiğiniz işlevin içinden erişilebilir olduğundan emin olmaktır.Process() . Bunu tipik olarak 'global' bir değişkenle elde edebilirsiniz.

Örnek kod:

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()

Windows'ta - desteklemeyen fork()- multiprocessingwin32 API çağrısını kullanıyor CreateProcess. Herhangi bir yürütülebilir dosyadan tamamen yeni bir süreç yaratır. Bu nedenle, Windows'ta , ebeveynin çalışma zamanı sırasında oluşturulmuş verilere ihtiyaç duyulması halinde, çocuğa veri toplaması gerekir.


3
Yazılırken kopyala, referans sayacını içeren sayfayı kopyalar (böylece her çatallı python kendi referans sayacına sahip olur), ancak tüm veri dizisini kopyalamaz.
robince

1
Modül seviyesi değişkenlerinde global değişkenlerden daha başarılı olduğumu ekleyeceğim ... yani değişkeni çataldan önce global kapsamdaki bir modüle ekleyin
2013

5
Bu soruya / cevaba rastlayan kişilere bir uyarı: Çok iş parçacıklı işlemi için OpenBLAS bağlantılı Numpy kullanıyorsanız, kullanım sırasında multithreading özelliğini devre dışı bıraktığınızdan emin olun (OPENBLAS_NUM_THREADS = 1 dışa aktarın) multiprocessingveya alt süreçler askıda kalabilir ( tipik olarak, paylaşılan bir global dizi / matris üzerinde doğrusal cebir işlemleri gerçekleştirdikten sonra n işlemci yerine bir işlemcinin 1 / n'sini kullanır . OpenBLAS ile bilinen okuyuculu çatışma Python uzatmak gibi görünüyormultiprocessing
Dologan

1
Python'un forkverilen parametreleri sıralamak Processyerine neden sadece işletim sistemini kullanmadığını açıklayan var mı? Yani, parametre değeri hala işletim sisteminden mevcut olacak şekilde çağrılmadan forkhemen önce üst işleme uygulanamaz childmı? Serileştirmekten daha verimli görünüyor mu?
en fazla

2
Hepimiz bunun fork()Windows'ta bulunmadığının farkındayız, cevabımda ve yorumlarda birçok kez belirtildi. Bunun için ilk soru olduğunu biliyorum ve dört comments yukarıda bunu yanıtladı bu : "uzlaşmanın daha iyi Düzeltilebilirlik için ve eşit davranış sağlamaktan varsayılan olarak her iki platformda da parametre transferi, aynı yöntemi kullanmaktır.". Her iki yolun da avantajları ve dezavantajları vardır, bu yüzden Python 3'te kullanıcının yöntemi seçmesi için daha fazla esneklik vardır. Bu tartışma, burada yapmamamız gereken ayrıntılar olmadan verimli değildir.
Dr.Jan-Philip Gehrcke

24

Yazdığım küçük bir kod parçası ilginizi çekebilir: github.com/vmlaker/benchmark-sharedmem

İlgilendiğiniz tek dosya main.py. Bu, numpy-sharedmem'in bir ölçütüdür - kod , Pipe aracılığıyla dizileri (ya numpyda sharedmem) oluşan süreçlere geçirir . İşçiler sadece sum()verileri çağırıyor . Sadece iki uygulama arasındaki veri iletişim sürelerini karşılaştırmakla ilgileniyordum.

Daha karmaşık başka bir kod da yazdım: github.com/vmlaker/sherlock .

Burada , OpenCV ile gerçek zamanlı görüntü işleme için numpy-sharedmem modülünü kullanıyorum - görüntüler, OpenCV'nin daha yeni cv2API'sine göre NumPy dizileridir . Görüntüler, aslında bunların referansları, oluşturulan sözlük nesnesi aracılığıyla işlemler arasında paylaşılır multiprocessing.Manager(Queue veya Pipe kullanmanın aksine) Düz NumPy dizilerini kullanmaya kıyasla mükemmel performans iyileştirmeleri alıyorum.

Boru ve Sıra :

Tecrübelerime göre, Pipe ile IPC Queue'dan daha hızlıdır. Ve bu mantıklı, çünkü Queue, birden fazla üretici / tüketici için güvenli hale getirmek için kilitlemeyi ekliyor. Pipe etmiyor. Ancak, karşılıklı konuşan yalnızca iki işleminiz varsa, Pipe'ı kullanmak güvenlidir veya belgelerin okuduğu gibi:

... aynı anda borunun farklı uçlarını kullanan işlemlerden kaynaklanan bozulma riski yoktur.

sharedmemgüvenlik :

sharedmemModülle ilgili temel sorun, programdan çıkıldığında bellek sızıntısı olasılığıdır. Bu, burada uzun bir tartışmada açıklanmaktadır . 10 Nisan 2011'de Sturla bellek sızıntısına yönelik bir düzeltmeden bahsetse de, o zamandan beri her iki depoyu, Sturla Molden'in GitHub'da ( github.com/sturlamolden/sharedmem-numpy ) ve Bitbucket'ta Chris Lee-Messer'in ( bitbucket.org/cleemesser/numpy-sharedmem ).


Teşekkürler, çok bilgilendirici. Yine de bellek sızıntısı sharedmemkulağa büyük bir sorun gibi geliyor. Bunu çözmek için herhangi bir ipucu var mı?
Will

1
Sızıntıları fark etmenin ötesinde, onu kodda aramadım. Yukarıdaki "paylaşılanmem güvenliği" altına, sharedmemmodülün iki açık kaynak deposunun koruyucularını referans olarak ekledim .
Velimir Mlaker

14

Diziniz o kadar büyükse kullanabilirsiniz numpy.memmap. Örneğin, diskte depolanan bir diziniz varsa, örneğin 'test.array'"yazma" modunda bile içindeki verilere erişmek için eşzamanlı işlemleri kullanabilirsiniz, ancak yalnızca "okuma" moduna ihtiyacınız olduğu için durumunuz daha basittir.

Dizinin oluşturulması:

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

Daha sonra bu diziyi sıradan bir dizide yaptığınız gibi doldurabilirsiniz. Örneğin:

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

Değişkeni sildiğinizde veriler diskte saklanır a.

Daha sonra, verilere erişecek birden çok işlem kullanabilirsiniz 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))

İlgili cevaplar:


3

Aynı makinedeki farklı çekirdeklerde ve farklı makinelerde farklı bölümleri yürütmek için görevinizi uygun şekilde bölümlere ayırabilirmişsiniz gibi pyro belgelerine göz atmayı da yararlı bulabilirsiniz .


0

Neden çoklu iş parçacığını kullanmıyorsunuz? Ana sürecin kaynakları kendi iş parçacıklarıyla yerel olarak paylaşılabilir, bu nedenle çoklu okuma, ana işlemin sahip olduğu nesneleri paylaşmanın daha iyi bir yoludur.

Eğer python en GiL mekanizması dert, belki için çare olabilir nogilarasında numba.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.