Bagikan Larik Numpy Besar dan Hanya Baca di Antara Proses Multiprosesing

90

Saya memiliki 60GB SciPy Array (Matrix) yang harus saya bagikan antara 5+ multiprocessing Processobjek. Saya telah melihat numpy-sharedmem dan membaca diskusi ini di daftar SciPy. Tampaknya ada dua pendekatan - numpy-sharedmemdan menggunakan a multiprocessing.RawArray()dan memetakan NumPy dtypes ke ctypes. Sekarang, numpy-sharedmemsepertinya cara untuk pergi, tapi saya belum melihat contoh referensi yang bagus. Saya tidak memerlukan kunci apa pun, karena array (sebenarnya matriks) akan menjadi hanya-baca. Sekarang, karena ukurannya, saya ingin menghindari salinannya. Ini terdengar seperti metode yang benar adalah untuk menciptakan hanya salinan array sebagai sharedmemarray, dan kemudian menyebarkannya ke Processobjek? Beberapa pertanyaan khusus:

  1. Apa cara terbaik untuk benar-benar meneruskan pegangan sharedmem ke sub- Process()es? Apakah saya memerlukan antrian hanya untuk melewatkan satu array? Apakah pipa lebih baik? Bisakah saya meneruskannya sebagai argumen ke Process()subkelas init (di mana saya menganggap itu acar)?

  2. Dalam diskusi yang saya tautkan di atas, ada yang menyebutkan numpy-sharedmemtidak aman 64bit? Saya pasti menggunakan beberapa struktur yang tidak dapat dialamatkan 32-bit.

  3. Apakah ada tradeoff untuk RawArray()pendekatan ini? Lebih lambat, buggier?

  4. Apakah saya memerlukan pemetaan ctype-to-dtype untuk metode numpy-sharedmem?

  5. Apakah ada yang punya contoh beberapa kode OpenSource melakukan ini? Saya sangat mahir belajar dan sulit untuk membuatnya bekerja tanpa contoh yang baik untuk dilihat.

Jika ada info tambahan yang dapat saya berikan untuk membantu mengklarifikasi ini untuk orang lain, silakan beri komentar dan saya akan menambahkan. Terima kasih!

Ini perlu dijalankan di Ubuntu Linux dan Mungkin Mac OS, tetapi portabilitas bukanlah masalah besar.

Akan
sumber
1
Jika proses yang berbeda akan menulis ke array itu, perkirakan multiprocessinguntuk membuat salinan semuanya untuk setiap proses.
tiago
3
@tiago: "Saya tidak memerlukan kunci apa pun, karena array (sebenarnya matriks) akan menjadi hanya-baca"
Dr. Jan-Philip Gehrcke
1
@tiago: juga, multiprocessing tidak membuat salinan selama tidak secara eksplisit diberitahu (melalui argumen ke target_function). Sistem operasi akan menyalin bagian dari memori orang tua ke ruang memori anak hanya dengan modifikasi.
Dr. Jan-Philip Gehrcke
Saya mengajukan beberapa pertanyaan tentang ini sebelumnya. Solusi saya dapat ditemukan di sini: github.com/david-hoffman/peaks/blob/… (maaf, kode ini adalah bencana).
David Hoffman

Jawaban:

30

@Velimir Mlaker memberikan jawaban yang bagus. Saya pikir saya bisa menambahkan sedikit komentar dan contoh kecil.

(Saya tidak dapat menemukan banyak dokumentasi tentang sharedmem - ini adalah hasil dari eksperimen saya sendiri.)

  1. Apakah Anda perlu memberikan pegangan saat subproses dimulai, atau setelah dimulai? Jika hanya yang pertama, Anda dapat menggunakan argumen targetdan argsuntuk Process. Ini berpotensi lebih baik daripada menggunakan variabel global.
  2. Dari halaman diskusi yang Anda tautkan, tampaknya dukungan untuk Linux 64-bit telah ditambahkan ke sharedmem beberapa waktu yang lalu, jadi ini mungkin bukan masalah.
  3. Saya tidak tahu tentang yang ini.
  4. Tidak. Lihat contoh di bawah.

Contoh

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

Keluaran

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

Pertanyaan terkait ini mungkin berguna.

James Lim
sumber
37

Jika Anda menggunakan Linux (atau sistem yang kompatibel dengan POSIX), Anda dapat mendefinisikan array ini sebagai variabel global. multiprocessingsedang digunakan fork()di Linux saat memulai proses anak baru. Proses turunan yang baru muncul secara otomatis membagikan memori dengan induknya selama tidak diubah ( mekanisme salin-saat-tulis ).

Karena Anda mengatakan "Saya tidak memerlukan kunci apa pun, karena array (sebenarnya matriks) akan menjadi hanya-baca", memanfaatkan perilaku ini akan menjadi pendekatan yang sangat sederhana namun sangat efisien: semua proses anak akan mengakses data yang sama dalam memori fisik saat membaca array numpy besar ini.

Jangan menyerahkan array Anda ke Process()konstruktor, ini akan menginstruksikan multiprocessinguntuk pickledata kepada anak, yang akan sangat tidak efisien atau tidak mungkin dalam kasus Anda. Di Linux, tepat setelah fork()anak adalah salinan persis dari induk yang menggunakan memori fisik yang sama, jadi yang perlu Anda lakukan adalah memastikan bahwa variabel Python 'yang berisi' matriks dapat diakses dari dalam targetfungsi yang Anda serahkan Process(). Ini biasanya dapat Anda capai dengan variabel 'global'.

Kode contoh:

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

Pada Windows - yang tidak mendukung fork()- multiprocessingmenggunakan panggilan API win32 CreateProcess. Ini menciptakan proses yang sama sekali baru dari apa pun yang dapat dieksekusi. Itulah mengapa pada Windows seseorang diharuskan untuk mengambil data ke anak jika seseorang membutuhkan data yang telah dibuat selama runtime dari induknya.

Dr. Jan-Philip Gehrcke
sumber
3
Copy-on-write akan menyalin halaman yang berisi penghitung referensi (jadi setiap python bercabang akan memiliki penghitung referensi sendiri) tetapi tidak akan menyalin seluruh larik data.
robince
1
Saya akan menambahkan bahwa saya lebih berhasil dengan variabel tingkat modul daripada dengan variabel global ... yaitu menambahkan variabel ke modul dalam lingkup global sebelum percabangan
robince
5
Perhatian bagi orang-orang yang tersandung pada pertanyaan / jawaban ini: Jika Anda kebetulan menggunakan Numpy yang terhubung dengan OpenBLAS untuk operasi multithreadeadnya, pastikan untuk menonaktifkan multithreading (ekspor OPENBLAS_NUM_THREADS = 1) saat menggunakan multiprocessingatau proses turunan mungkin berakhir hang ( biasanya menggunakan 1 / n dari satu prosesor daripada n prosesor) saat melakukan operasi aljabar linier pada matriks / array global bersama. The multithreaded konflik yang dikenal dengan OpenBLAS tampaknya meluas ke Pythonmultiprocessing
Dologan
1
Adakah yang bisa menjelaskan mengapa python tidak hanya menggunakan OS forkuntuk meneruskan parameter yang diberikan Process, daripada serialisasi mereka? Artinya, tidak bisakah forkditerapkan ke proses induk sebelum child dipanggil, sehingga nilai parameter masih tersedia dari OS? Apakah tampaknya lebih efisien daripada serialisasi itu?
maks
2
Kami semua menyadari bahwa fork()itu tidak tersedia di Windows, itu telah dinyatakan dalam jawaban saya dan beberapa kali di komentar. Saya tahu bahwa ini adalah pertanyaan awal Anda, dan saya menjawabnya dengan empat komentar di atas ini : "kompromi adalah menggunakan metode transfer parameter yang sama pada kedua platform secara default, untuk pemeliharaan yang lebih baik dan untuk memastikan perilaku yang sama.". Kedua cara memiliki kelebihan dan kekurangan, itulah sebabnya di Python 3 ada fleksibilitas yang lebih besar bagi pengguna untuk memilih metode. Diskusi ini tidak produktif tanpa membicarakan detail, yang seharusnya tidak kita lakukan di sini.
Dr. Jan-Philip Gehrcke
24

Anda mungkin tertarik dengan sepotong kecil kode yang saya tulis: github.com/vmlaker/benchmark-sharedmem

Satu-satunya file yang menarik adalah main.py. Ini adalah tolok ukur dari numpy-sharedmem - kodenya hanya meneruskan array (baik numpyatau sharedmem) ke proses yang muncul, melalui Pipe. Para pekerja baru saja memanggil sum()data. Saya hanya tertarik untuk membandingkan waktu komunikasi data antara dua implementasi.

Saya juga menulis kode lain yang lebih kompleks: github.com/vmlaker/sherlock .

Di sini saya menggunakan modul numpy-sharedmem untuk pemrosesan gambar real-time dengan OpenCV - gambarnya adalah array NumPy, sesuai cv2API OpenCV yang lebih baru . Gambar, sebenarnya referensi darinya, dibagikan antara proses melalui objek kamus yang dibuat dari multiprocessing.Manager(sebagai lawan menggunakan Antrian atau Pipa.) Saya mendapatkan peningkatan kinerja yang luar biasa jika dibandingkan dengan menggunakan array NumPy biasa.

Pipa vs. Antrian :

Menurut pengalaman saya, IPC dengan Pipa lebih cepat daripada Antrian. Dan itu masuk akal, karena Antrian menambahkan penguncian untuk membuatnya aman bagi banyak produsen / konsumen. Pipa tidak. Tetapi jika Anda hanya memiliki dua proses yang berbicara bolak-balik, aman untuk menggunakan Pipe, atau, seperti yang dibaca dokumen:

... tidak ada risiko korupsi dari proses yang menggunakan ujung pipa yang berbeda secara bersamaan.

sharedmemkeamanan :

Masalah utama dengan sharedmemmodul adalah kemungkinan kebocoran memori saat keluar dari program yang tidak terdeteksi. Ini dijelaskan dalam diskusi panjang di sini . Meskipun pada 10 Apr 2011 Sturla menyebutkan perbaikan untuk kebocoran memori, saya masih mengalami kebocoran sejak saat itu, menggunakan kedua repo, Sturla Molden sendiri di GitHub ( github.com/sturlamolden/sharedmem-numpy ) dan Chris Lee-Messer di Bitbucket ( bitbucket.org/cleemesser/numpy-sharedmem ).

Velimir Mlaker
sumber
Terima kasih, sangat informatif. Namun, kebocoran memori sharedmemterdengar seperti masalah besar. Ada petunjuk untuk memecahkannya?
Akankah
1
Selain hanya memperhatikan kebocorannya, saya belum mencarinya di kode. Saya menambahkan jawaban saya, di bawah "sharedmem safety" di atas, penjaga dari dua repo sharedmemmodul open source , untuk referensi.
Velimir Mlaker
14

Jika array Anda sebesar itu, Anda dapat menggunakan numpy.memmap. Misalnya, jika Anda memiliki larik yang disimpan dalam disk, katakanlah 'test.array', Anda dapat menggunakan proses simultan untuk mengakses data di dalamnya bahkan dalam mode "menulis", tetapi kasus Anda lebih sederhana karena Anda hanya memerlukan mode "membaca".

Membuat array:

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

Anda kemudian dapat mengisi larik ini dengan cara yang sama seperti yang Anda lakukan dengan larik biasa. Sebagai contoh:

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

Data disimpan ke dalam disk saat Anda menghapus variabel a.

Nanti Anda dapat menggunakan beberapa proses yang akan mengakses data di 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))

Jawaban terkait:

Saullo GP Castro
sumber
3

Anda mungkin juga merasa berguna untuk melihat dokumentasi untuk pyro seolah-olah Anda dapat mempartisi tugas Anda dengan tepat, Anda dapat menggunakannya untuk menjalankan bagian yang berbeda pada mesin yang berbeda serta pada inti yang berbeda di mesin yang sama.

Steve Barnes
sumber
0

Mengapa tidak menggunakan multithreading? Sumber daya proses utama dapat dibagikan oleh utasnya secara asli, sehingga multithreading jelas merupakan cara yang lebih baik untuk berbagi objek yang dimiliki oleh proses utama.

Jika Anda khawatir tentang mekanisme GIL python, mungkin Anda dapat menggunakan nogildari numba.

Nico
sumber