multiprocessing: berbagi objek read-only yang besar di antara proses?

107

Apakah proses anak muncul melalui objek berbagi multiprosesing yang dibuat sebelumnya dalam program?

Saya memiliki pengaturan berikut:

do_some_processing(filename):
    for line in file(filename):
        if line.split(',')[0] in big_lookup_object:
            # something here

if __name__ == '__main__':
    big_lookup_object = marshal.load('file.bin')
    pool = Pool(processes=4)
    print pool.map(do_some_processing, glob.glob('*.data'))

Saya memuat beberapa objek besar ke dalam memori, lalu membuat kumpulan pekerja yang perlu menggunakan objek besar itu. Objek besar diakses hanya-baca, saya tidak perlu meneruskan modifikasinya di antara proses.

Pertanyaan saya adalah: apakah objek besar dimuat ke dalam memori bersama, seperti jika saya menelurkan proses di unix / c, atau apakah setiap proses memuat salinan objek besar itu sendiri?

Pembaruan: untuk memperjelas lebih lanjut - big_lookup_object adalah objek pencarian bersama. Saya tidak perlu membaginya dan memprosesnya secara terpisah. Saya perlu menyimpan satu salinannya. Pekerjaan yang perlu saya bagi adalah membaca banyak file besar lainnya dan mencari item dalam file besar tersebut terhadap objek pencarian.

Pembaruan lebih lanjut: database adalah solusi yang bagus, memcache mungkin solusi yang lebih baik, dan file di disk (rak atau dbm) mungkin lebih baik. Dalam pertanyaan ini saya sangat tertarik pada solusi memori. Untuk solusi terakhir, saya akan menggunakan hadoop, tetapi saya ingin melihat apakah saya dapat memiliki versi dalam memori lokal juga.

Parand
sumber
kode Anda seperti yang tertulis akan memanggil marshal.loadorang tua dan untuk setiap anak (setiap proses mengimpor modul).
jfs
Anda benar, dikoreksi.
Parand
Untuk "memori lokal" dan jika Anda ingin menghindari penyalinan, berikut ini mungkin berguna docs.python.org/library/…
jfs
berbagi no. proses yang muncul (misalnya fork atau exec) adalah duplikat yang tepat dari proses pemanggilan ... tetapi dalam memori yang berbeda. Untuk satu proses untuk berbicara dengan yang lain, Anda memerlukan komunikasi antarproses atau pembacaan / penulisan IPC ke beberapa lokasi memori bersama .
ron

Jawaban:

50

"Apakah proses turunan muncul melalui objek berbagi multiprosesing yang dibuat sebelumnya dalam program?"

Tidak (python sebelum 3.8), dan Ya di 3.8 ( https://docs.python.org/3/library/multiprocessing.shared_memory.html#module-multiprocessing.shared_memory )

Proses memiliki ruang memori independen.

Solusi 1

Untuk memanfaatkan sebaik-baiknya struktur besar dengan banyak pekerja, lakukan ini.

  1. Tulis setiap pekerja sebagai "filter" - membaca hasil antara dari stdin, berfungsi, menulis hasil antara di stdout.

  2. Hubungkan semua pekerja sebagai pipa:

    process1 <source | process2 | process3 | ... | processn >result

Setiap proses membaca, bekerja, dan menulis.

Ini sangat efisien karena semua proses berjalan secara bersamaan. Penulisan dan pembacaan melewati langsung buffer bersama di antara proses.


Solusi 2

Dalam beberapa kasus, Anda memiliki struktur yang lebih kompleks - seringkali struktur "menyebar". Dalam hal ini Anda memiliki orang tua dengan banyak anak.

  1. Induk membuka data sumber. Orang tua bercabang untuk sejumlah anak.

  2. Induk membaca sumber, mengolah bagian-bagian dari sumber ke setiap turunan yang berjalan secara bersamaan.

  3. Saat induk mencapai ujung, tutup pipa. Anak mendapatkan akhir file dan selesai secara normal.

Bagian-bagian anak menyenangkan untuk ditulis karena setiap anak hanya membaca sys.stdin.

Orang tua memiliki sedikit gerakan kaki yang bagus dalam memijah semua anak dan mempertahankan pipa dengan benar, tetapi itu tidak terlalu buruk.

Fan-in adalah struktur kebalikannya. Sejumlah proses yang berjalan secara independen perlu menyisipkan masukan mereka ke dalam proses umum. Seorang kolektor tidak mudah menulis, karena harus membaca dari berbagai sumber.

Membaca dari banyak pipa bernama sering dilakukan dengan menggunakan selectmodul untuk melihat pipa mana yang memiliki input tertunda.


Solusi 3

Pencarian bersama adalah definisi database.

Solusi 3A - muat database. Biarkan pekerja memproses data di database.

Solusi 3B - buat server yang sangat sederhana menggunakan werkzeug (atau serupa) untuk menyediakan aplikasi WSGI yang merespons HTTP GET sehingga pekerja dapat melakukan kueri ke server.


Solusi 4

Objek sistem file bersama. Unix OS menawarkan objek memori bersama. Ini hanyalah file yang dipetakan ke memori sehingga pertukaran I / O dilakukan alih-alih membaca buffer konvensi.

Anda dapat melakukan ini dari konteks Python dengan beberapa cara

  1. Tulis program startup yang (1) memecah objek raksasa asli Anda menjadi objek yang lebih kecil, dan (2) memulai pekerja, masing-masing dengan objek yang lebih kecil. Objek yang lebih kecil dapat dijadikan acar objek Python untuk menghemat sedikit waktu membaca file.

  2. Tulis program startup yang (1) membaca objek raksasa asli Anda dan menulis file berkode byte terstruktur halaman menggunakan seekoperasi untuk memastikan bahwa setiap bagian mudah ditemukan dengan pencarian sederhana. Inilah yang dilakukan mesin database - memecah data menjadi beberapa halaman, membuat setiap halaman mudah ditemukan melalui file seek.

    Menelurkan pekerja dengan akses file berstruktur halaman besar ini. Setiap pekerja dapat mencari bagian yang relevan dan melakukan pekerjaan mereka di sana.

S. Lott
sumber
Proses saya tidak benar-benar fitler; semuanya sama, hanya memproses bagian data yang berbeda.
Parand
Mereka sering kali dapat disusun sebagai filter. Mereka membaca bagian data mereka, melakukan pekerjaan mereka, dan menulis hasilnya untuk diproses nanti.
S. Lott
Saya suka solusi Anda, tetapi apa yang terjadi dengan pemblokiran I / O? Bagaimana jika orang tua menghalangi membaca / menulis dari / ke salah satu anaknya? Select tidak memberi tahu Anda bahwa Anda dapat menulis, tetapi tidak mengatakan seberapa banyak. Sama halnya dengan membaca.
Cristian Ciupitu
Ini adalah proses yang terpisah - orang tua dan anak tidak saling mengganggu. Setiap byte yang dihasilkan di salah satu ujung pipa segera tersedia di ujung lainnya untuk dikonsumsi - pipa adalah buffer bersama. Tidak yakin apa arti pertanyaan Anda dalam konteks ini.
S. Lotot
Saya dapat memverifikasi apa yang dikatakan S. Lott. Saya membutuhkan operasi yang sama dilakukan pada satu file. Jadi pekerja pertama menjalankan fungsinya pada setiap baris dengan nomor% 2 == 0 dan menyimpannya ke sebuah file, dan mengirim baris lain ke proses berikutnya (yang merupakan skrip yang sama). Waktu kerja turun setengah. Ini sedikit hacky, tapi overhead-nya jauh lebih ringan daripada map / poop di modul multiprocessing.
Vince
36

Apakah proses anak muncul melalui objek berbagi multiprosesing yang dibuat sebelumnya dalam program?

Tergantung. Untuk variabel read-only global sering dianggap demikian (selain dari memori yang dikonsumsi) jika tidak.

dokumentasi multiprocessing mengatakan:

Better to inherit than pickle/unpickle

Pada Windows, banyak jenis dari multiprocessing harus dapat dipilih sehingga proses anak dapat menggunakannya. Namun, orang biasanya harus menghindari pengiriman objek bersama ke proses lain menggunakan pipa atau antrian. Sebaliknya, Anda harus mengatur program sehingga proses yang memerlukan akses ke sumber daya bersama yang dibuat di tempat lain dapat mewarisinya dari proses leluhur.

Explicitly pass resources to child processes

Di Unix, proses anak dapat menggunakan sumber daya bersama yang dibuat dalam proses induk menggunakan sumber daya global. Namun, lebih baik meneruskan objek sebagai argumen ke konstruktor untuk proses anak.

Selain membuat kode (berpotensi) kompatibel dengan Windows, ini juga memastikan bahwa selama proses turunan masih hidup, objek tidak akan dibuang ke sampah yang dikumpulkan dalam proses induk. Ini mungkin penting jika beberapa sumber daya dibebaskan saat objek tersebut dikumpulkan dari sampah dalam proses induk.

Global variables

Ingatlah bahwa jika kode yang dijalankan dalam proses anak mencoba mengakses variabel global, maka nilai yang dilihatnya (jika ada) mungkin tidak sama dengan nilai dalam proses induk pada saat Process.start () dipanggil .

Contoh

Di Windows (CPU tunggal):

#!/usr/bin/env python
import os, sys, time
from multiprocessing import Pool

x = 23000 # replace `23` due to small integers share representation
z = []    # integers are immutable, let's try mutable object

def printx(y):
    global x
    if y == 3:
       x = -x
    z.append(y)
    print os.getpid(), x, id(x), z, id(z) 
    print y
    if len(sys.argv) == 2 and sys.argv[1] == "sleep":
       time.sleep(.1) # should make more apparant the effect

if __name__ == '__main__':
    pool = Pool(processes=4)
    pool.map(printx, (1,2,3,4))

Dengan sleep:

$ python26 test_share.py sleep
2504 23000 11639492 [1] 10774408
1
2564 23000 11639492 [2] 10774408
2
2504 -23000 11639384 [1, 3] 10774408
3
4084 23000 11639492 [4] 10774408
4

Tanpa sleep:

$ python26 test_share.py
1148 23000 11639492 [1] 10774408
1
1148 23000 11639492 [1, 2] 10774408
2
1148 -23000 11639324 [1, 2, 3] 10774408
3
1148 -23000 11639324 [1, 2, 3, 4] 10774408
4
jfs
sumber
6
Hah? Bagaimana z dibagikan di seluruh proses ??
cbare
4
@cbare: Pertanyaan bagus! z sebenarnya tidak dibagikan, seperti yang ditunjukkan oleh output dengan sleep. Output tanpa tidur menunjukkan bahwa satu proses menangani (PID = 1148) semua pekerjaan; apa yang kita lihat di contoh terakhir adalah nilai z untuk proses tunggal ini.
Eric O Lebigot
Jawaban ini menunjukkan bahwa ztidak dibagikan. Ini dengan demikian menjawab pertanyaan dengan: "tidak, di bawah Windows setidaknya, variabel induk tidak dibagi antara anak-anak".
Eric O Lebigot
@EOL: secara teknis Anda benar tetapi dalam praktiknya jika data hanya-baca (tidak seperti zkasus) itu dapat dianggap dibagikan.
jfs
Hanya untuk memperjelas, pernyataan tersebut Ingatlah bahwa jika kode dijalankan dalam proses anak mencoba mengakses variabel global ... di dokumen 2.7 mengacu pada Python yang berjalan di bawah Windows.
pengguna1071847
28

S. Lott benar. Pintasan multiprosesing Python secara efektif memberi Anda potongan memori yang terpisah dan digandakan.

Pada kebanyakan sistem * nix, menggunakan panggilan tingkat rendah ke os.fork()akan, pada kenyataannya, memberi Anda memori copy-on-write, yang mungkin seperti yang Anda pikirkan. AFAIK, secara teori, dalam program yang paling sederhana, Anda dapat membaca dari data itu tanpa membuatnya digandakan.

Namun, hal-hal tidak sesederhana itu dalam interpreter Python. Data objek dan meta-data disimpan dalam segmen memori yang sama, jadi meskipun objek tidak pernah berubah, sesuatu seperti penghitung referensi untuk objek yang bertambah akan menyebabkan penulisan memori, dan karenanya akan disalin. Hampir semua program Python yang melakukan lebih dari "print 'hello'" akan menyebabkan peningkatan jumlah referensi, jadi Anda mungkin tidak akan pernah menyadari manfaat dari copy-on-write.

Bahkan jika seseorang berhasil meretas solusi memori bersama dengan Python, mencoba mengoordinasikan pengumpulan sampah di seluruh proses mungkin akan sangat menyakitkan.

Jarret Hardie
sumber
3
Hanya wilayah memori dari hitungan ref yang akan disalin dalam kasus itu, belum tentu data hanya-baca yang besar, bukan?
kawing-chiu
7

Jika Anda menjalankan di bawah Unix, mereka mungkin berbagi objek yang sama, karena cara kerja fork (yaitu, proses anak memiliki memori terpisah tetapi ini adalah copy-on-write, jadi itu dapat dibagikan selama tidak ada yang memodifikasinya). Saya mencoba yang berikut ini:

import multiprocessing

x = 23

def printx(y):
    print x, id(x)
    print y

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    pool.map(printx, (1,2,3,4))

dan mendapatkan hasil sebagai berikut:

$ ./mtest.py
23 22995656
1
23 22995656
2
23 22995656
3
23 22995656
4

Tentu saja ini tidak membuktikan bahwa salinan belum dibuat, tetapi Anda harus dapat memverifikasi itu dalam situasi Anda dengan melihat output dari psuntuk melihat berapa banyak memori nyata yang digunakan setiap subproses.

Jacob Gabrielson
sumber
2
Bagaimana dengan pemulung? Apa yang terjadi saat itu berjalan? Bukankah tata letak memori berubah?
Cristian Ciupitu
Itu perhatian yang valid. Apakah itu akan memengaruhi Parand akan bergantung pada bagaimana dia menggunakan semua ini dan seberapa andal kode ini. Jika tidak berhasil untuknya, saya akan merekomendasikan menggunakan modul mmap untuk kontrol lebih (dengan asumsi dia ingin tetap menggunakan pendekatan dasar ini).
Jacob Gabrielson
Saya telah memposting pembaruan ke contoh Anda: stackoverflow.com/questions/659865/…
jfs
@JacobGabrielson: Salinan sudah dibuat. Pertanyaan aslinya adalah tentang apakah salinan itu dibuat.
abhinavkulkarni
3

Proses yang berbeda memiliki ruang alamat yang berbeda. Seperti menjalankan berbagai contoh penerjemah. Itulah gunanya IPC (komunikasi antarproses).

Anda dapat menggunakan antrian atau pipa untuk tujuan ini. Anda juga dapat menggunakan rpc melalui tcp jika Anda ingin mendistribusikan proses melalui jaringan nanti.

http://docs.python.org/dev/library/multiprocessing.html#exchanging-objects-between-processes

Vasil
sumber
2
Saya tidak berpikir IPC akan sesuai untuk ini; ini adalah data hanya-baca yang perlu diakses semua orang. Tidak ada gunanya menyebarkannya di antara proses; paling buruk masing-masing dapat membaca salinannya sendiri. Saya mencoba menghemat memori dengan tidak memiliki salinan terpisah di setiap proses.
Parand
Anda dapat memiliki proses master yang mendelegasikan potongan data untuk dikerjakan ke proses slave lainnya. Baik budak dapat meminta data atau dapat mendorong data. Dengan cara ini tidak setiap proses akan memiliki salinan dari keseluruhan objek.
Vasil
1
@Vasil: Bagaimana jika setiap proses membutuhkan seluruh kumpulan data, dan hanya menjalankan operasi yang berbeda?
Akankah
1

Tidak terkait langsung dengan multiprocessing itu sendiri, tetapi dari contoh Anda, sepertinya Anda bisa menggunakan modul rak atau semacamnya. Apakah "big_lookup_object" harus benar-benar ada di memori?


sumber
Poin bagus, saya belum membandingkan secara langsung kinerja on-disk vs. dalam memori. Saya berasumsi akan ada perbedaan besar, tetapi saya belum benar-benar menguji.
Parand
1

Tidak, tetapi Anda dapat memuat data Anda sebagai proses anak dan mengizinkannya untuk membagikan datanya dengan anak lain. Lihat di bawah.

import time
import multiprocessing

def load_data( queue_load, n_processes )

    ... load data here into some_variable

    """
    Store multiple copies of the data into
    the data queue. There needs to be enough
    copies available for each process to access. 
    """

    for i in range(n_processes):
        queue_load.put(some_variable)


def work_with_data( queue_data, queue_load ):

    # Wait for load_data() to complete
    while queue_load.empty():
        time.sleep(1)

    some_variable = queue_load.get()

    """
    ! Tuples can also be used here
    if you have multiple data files
    you wish to keep seperate.  
    a,b = queue_load.get()
    """

    ... do some stuff, resulting in new_data

    # store it in the queue
    queue_data.put(new_data)


def start_multiprocess():

    n_processes = 5

    processes = []
    stored_data = []

    # Create two Queues
    queue_load = multiprocessing.Queue()
    queue_data = multiprocessing.Queue()

    for i in range(n_processes):

        if i == 0:
            # Your big data file will be loaded here...
            p = multiprocessing.Process(target = load_data,
            args=(queue_load, n_processes))

            processes.append(p)
            p.start()   

        # ... and then it will be used here with each process
        p = multiprocessing.Process(target = work_with_data,
        args=(queue_data, queue_load))

        processes.append(p)
        p.start()

    for i in range(n_processes)
        new_data = queue_data.get()
        stored_data.append(new_data)    

    for p in processes:
        p.join()
    print(processes)    
Mott The Tuple
sumber
-4

Untuk platform Linux / Unix / MacOS, forkmap adalah solusi cepat dan kotor.

Maxim Imakaev
sumber