çoklu işlem: işlemler arasında büyük bir salt okunur nesnenin paylaşılması?


107

Alt süreçler , programda daha önce oluşturulmuş nesneleri paylaşan çoklu işlem aracılığıyla mı ortaya çıkıyor ?

Aşağıdaki kuruluma sahibim:

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

Belleğe bazı büyük nesneler yüklüyorum, sonra o büyük nesneyi kullanması gereken bir işçi havuzu oluşturuyorum. Büyük nesneye salt okunur erişiliyor, işlemler arasında değişiklik yapmama gerek yok.

Sorum şu: paylaşılan belleğe büyük nesne, unix / c'de bir süreç oluşturduğumda olduğu gibi mi yükleniyor yoksa her işlem büyük nesnenin kendi kopyasını mı yüklüyor?

Güncelleme: daha fazla açıklığa kavuşturmak için - big_lookup_object paylaşılan bir arama nesnesidir. Bunu bölmeye ve ayrı ayrı işlemeye ihtiyacım yok. Tek bir kopyasını tutmam gerekiyor. Bölmem gereken iş, diğer birçok büyük dosyayı okumak ve bu büyük dosyalardaki öğeleri arama nesnesine göre aramaktır.

Daha fazla güncelleme: veritabanı iyi bir çözüm, memcached daha iyi bir çözüm olabilir ve diskteki dosya (raf veya dbm) daha da iyi olabilir. Bu soruda özellikle bellek içi bir çözümle ilgilendim. Nihai çözüm için hadoop kullanacağım, ancak yerel bir bellek içi sürüme de sahip olup olamayacağımı görmek istedim.


Kodunuz yazıldığı marshal.loadşekliyle ebeveyn ve her çocuk için gerekli olacaktır (her işlem modülü içe aktarır).
jfs

Haklısın, düzeltildi.
Parand

"Yerel bellek içi" için ve kopyalamaktan kaçınmak istiyorsanız aşağıdakiler yararlı olabilir docs.python.org/library/…
jfs

hayır paylaşmak. ortaya çıkan süreçler (örneğin fork veya exec) , çağıran sürecin tam bir kopyasıdır ... ancak farklı bellekte. Bir işlemin diğeriyle konuşması için, işlemler arası iletişim veya IPC'nin bazı paylaşılan bellek konumuna okuma / yazma ihtiyacı duyarsınız .
ron

Yanıtlar:


50

"Çocuk süreçler, programda daha önce oluşturulan nesneleri paylaşıyor mu?"

Hayır (3.8'den önce python) ve 3.8'de Evet ( https://docs.python.org/3/library/multiprocessing.shared_memory.html#module-multiprocessing.shared_memory )

İşlemlerin bağımsız bellek alanı vardır.

1.Çözüm

Çok sayıda işçi bulunan büyük bir yapıyı en iyi şekilde kullanmak için bunu yapın.

  1. Her çalışanı bir "filtre" olarak yazın - stdin'den ara sonuçları okur, çalışır, ara sonuçları stdout'a yazar.

  2. Tüm çalışanları bir boru hattı olarak bağlayın:

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

Her süreç okur, çalışır ve yazar.

Bu, tüm süreçler aynı anda çalıştığı için oldukça verimlidir. Yazma ve okuma işlemleri, işlemler arasındaki paylaşılan tamponlardan doğrudan geçer.


2.Çözüm

Bazı durumlarda, daha karmaşık bir yapıya sahipsiniz - genellikle bir "yayılma" yapısı. Bu durumda birden fazla çocuğu olan bir ebeveyniniz vardır.

  1. Üst öğe, kaynak verileri açar. Ebeveyn birkaç çocuğu çatallar.

  2. Ebeveyn kaynağı okur, kaynağın parçalarını eşzamanlı olarak çalışan her çocuğa dağıtır.

  3. Ebeveyn sona ulaştığında boruyu kapatın. Çocuk dosyanın sonuna gelir ve normal şekilde bitirir.

Çocuk bölümleri yazmak hoş çünkü her çocuk basitçe okuyor sys.stdin.

Ebeveynin, tüm çocukları yumurtlama ve boruları düzgün bir şekilde tutma konusunda biraz süslü ayak çalışması var, ama bu çok da kötü değil.

Fan-in zıt yapıdır. Bağımsız olarak çalışan bir dizi işlemin girdilerini ortak bir sürece eklemesi gerekir. Koleksiyoncu, birçok kaynaktan okumak zorunda olduğu için yazmak o kadar kolay değildir.

Birçok adlandırılmış kanaldan okuma, selecthangi kanalların bekleyen girdiye sahip olduğunu görmek için genellikle modül kullanılarak yapılır .


3. Çözüm

Paylaşılan arama, bir veritabanının tanımıdır.

Çözüm 3A - bir veritabanı yükleyin. Çalışanların veri tabanındaki verileri işlemesine izin verin.

Çözüm 3B - çalışanların sunucuyu sorgulayabilmesi için HTTP GET'e yanıt veren WSGI uygulamaları sağlamak için werkzeug (veya benzerini) kullanarak çok basit bir sunucu oluşturun .


4.Çözüm

Paylaşılan dosya sistemi nesnesi. Unix OS, paylaşılan bellek nesneleri sunar. Bunlar yalnızca belleğe eşlenen dosyalardır, böylece daha fazla arabelleğe alınmış okuma yerine G / Ç değiştirilir.

Bunu bir Python bağlamından birkaç şekilde yapabilirsiniz

  1. (1) orijinal devasa nesnenizi daha küçük nesnelere bölen ve (2) her biri daha küçük bir nesneyle çalışanları başlatan bir başlangıç ​​programı yazın. Küçük nesneler, küçük bir dosya okuma süresinden tasarruf etmek için Python nesneleriyle temizlenebilir.

  2. (1) orijinal devasa nesnenizi okuyan ve seektek tek bölümlerin basit aramalarla kolayca bulunmasını sağlamak için işlemleri kullanarak sayfa yapılı, bayt kodlu bir dosya yazan bir başlangıç ​​programı yazın . Bir veritabanı motorunun yaptığı şey budur - verileri sayfalara böler, her sayfanın bir seek.

    Bu büyük sayfa yapılı dosyaya erişen işçiler oluşturun. Her işçi ilgili bölümleri arayabilir ve orada işini yapabilir.


Süreçlerim gerçekten uyumlu değil; hepsi aynı, sadece farklı veri parçalarını işliyor.
Parand

Genellikle filtreler olarak yapılandırılabilirler. Verilerini okurlar, işlerini yaparlar ve daha sonra işlenmek üzere sonuçlarını yazarlar.
S.Lott

Çözümünüzü beğendim, ancak engelleyen G / Ç'ye ne olacak? Ya ebeveyn çocuklarından birine / çocuklarına okumayı / yazmayı engellerse? Select yazabileceğinizi size bildirmez, ancak ne kadar olduğunu belirtmez. Okumak için de aynı.
Cristian Ciupitu

Bunlar ayrı süreçlerdir - ebeveynler ve çocuklar birbirine karışmaz. Bir borunun bir ucunda üretilen her bir bayt, diğer ucunda hemen tüketilmek üzere kullanılabilir - bir boru, paylaşılan bir tampondur. Sorunuzun bu bağlamda ne anlama geldiğinden emin değilim.
S.Lott

S.Lott'un ne dediğini doğrulayabilirim. Aynı işlemlerin tek bir dosyada yapılmasına ihtiyacım vardı. Böylece, ilk işçi% 2 == 0 numaralı her satırda işlevini çalıştırdı ve bir dosyaya kaydetti ve diğer satırları bir sonraki borulu işleme (aynı betikti) gönderdi. Çalışma süresi yarı yarıya azaldı. Biraz karmaşık, ancak ek yük, çoklu işlem modülündeki map / poop'tan çok daha hafif.
Vince

36

Alt süreçler , programda daha önce oluşturulmuş nesneleri paylaşan çoklu işlem aracılığıyla mı ortaya çıkıyor ?

Değişir. Genel salt okunur değişkenler için, genellikle bu şekilde kabul edilebilir (tüketilen bellek dışında), aksi takdirde olmamalıdır.

multiprocessing belgelerine göre:

Better to inherit than pickle/unpickle

Windows'ta, alt işlemlerin kullanabilmesi için çoklu işlemden birçok türün seçilebilir olması gerekir. Ancak, paylaşılan nesnelerin diğer işlemlere boru veya kuyruklar kullanılarak gönderilmesinden genellikle kaçınılmalıdır. Bunun yerine programı, başka bir yerde oluşturulan paylaşılan bir kaynağa erişime ihtiyaç duyan bir işlem, onu bir üst süreçten devralacak şekilde düzenlemelisiniz.

Explicitly pass resources to child processes

Unix'te bir çocuk süreç, genel bir kaynak kullanılarak bir üst süreçte oluşturulan paylaşılan bir kaynağı kullanabilir. Bununla birlikte, nesneyi alt süreç için yapıcıya bir argüman olarak iletmek daha iyidir.

Bu, kodu (potansiyel olarak) Windows ile uyumlu hale getirmenin yanı sıra, çocuk süreç hala canlı olduğu sürece nesnenin üst süreçte çöp toplanmamasını sağlar. Nesne üst işlemde çöp toplandığında bazı kaynaklar serbest bırakılırsa bu önemli olabilir.

Global variables

Bir alt süreçte çalıştırılan kod bir global değişkene erişmeye çalışırsa, gördüğü değerin (varsa) Process.start () çağrıldığı sırada üst süreçteki değerle aynı olmayabileceğini unutmayın. .

Misal

Windows'ta (tek CPU):

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

İle 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

Olmadan 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

6
Huh? Z süreçler arasında nasıl paylaşılıyor?
cbare

4
@cbare: Güzel soru! Aslında z, uyku ile çıktının gösterdiği gibi paylaşılmaz. Uykusuz çıktı, tüm işi tek bir işlemin (PID = 1148) işlediğini gösterir ; son örnekte gördüğümüz şey, bu tek işlem için z'nin değeridir.
Eric O Lebigot

Bu cevap zbunun paylaşılmadığını gösteriyor. Bu, soruyu şu şekilde yanıtlar: "hayır, en azından Windows altında, bir ana değişken çocuklar arasında paylaşılmaz".
Eric O Lebigot 01

@EOL: teknik olarak haklısınız, ancak pratikte veriler salt okunursa ( zdurumun aksine ) paylaşılmış sayılabilir.
jfs

Sadece açıklığa kavuşturmak için, eğer bir alt süreçte çalıştırılan kod global bir değişkene erişmeye çalışırsa ... 2.7 belgelerinde Windows altında çalışan Python'a atıfta bulunulacağını unutmayın.
user1071847

28

S.Lott haklı . Python'un çoklu işlem kısayolları size ayrı, çoğaltılmış bir bellek parçası sağlar.

Çoğu * nix sisteminde, daha düşük seviyeli bir çağrı kullanmak os.fork(), aslında size yazma üzerine kopyalama belleği verir, bu da düşündüğünüz şey olabilir. AFAIK, teorik olarak, mümkün olan en basit programlarda, bu verileri kopyalamadan okuyabiliyordunuz.

Ancak Python yorumlayıcısında işler o kadar basit değildir. Nesne verileri ve meta veriler aynı bellek bölümünde depolanır, bu nedenle nesne hiç değişmese bile, bu nesne için bir referans sayacı gibi artırılan bir şey bir belleğe ve dolayısıyla bir kopyaya neden olur. "Yazdır" merhaba "dan daha fazlasını yapan hemen hemen her Python programı, referans sayısı artışlarına neden olacaktır, bu nedenle, yazma üzerine kopyalama avantajını büyük olasılıkla asla fark etmeyeceksiniz.

Birisi Python'da paylaşılan bir bellek çözümünü hacklemeyi başarsa bile, süreçler arasında çöp toplamayı koordine etmeye çalışmak muhtemelen oldukça acı verici olacaktır.


3
Bu durumda sadece referans sayısının hafıza bölgesi kopyalanacaktır, salt okunur büyük veri olması gerekmez, değil mi?
kawing-chiu

7

Unix altında çalışıyorsanız, forkun nasıl çalıştığına bağlı olarak aynı nesneyi paylaşabilirler (yani, alt süreçlerin ayrı hafızası vardır, ancak yazılırken kopyalanır, bu nedenle kimse değiştirmediği sürece paylaşılabilir). Aşağıdakileri denedim:

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

ve aşağıdaki çıktıyı aldı:

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

Elbette bu bir kopyanın yapılmadığını kanıtlamaz , ancak kendi durumunuzda, psher bir alt işlemin ne kadar gerçek bellek kullandığını görmek için ürününün çıktısına bakarak bunu doğrulayabilmelisiniz .


2
Çöp toplayıcı ne olacak? Çalıştığı zaman ne olur? Hafıza düzeni değişmiyor mu?
Cristian Ciupitu

Bu geçerli bir endişe. Parand'ı etkileyip etkilemeyeceği, tüm bunları nasıl kullandığına ve bu kodun ne kadar güvenilir olması gerektiğine bağlı olacaktır. Onun için işe yaramıyorsa, daha fazla kontrol için mmap modülünü kullanmanızı tavsiye ederim (bu temel yaklaşıma bağlı kalmak istediğini varsayarak).
Jacob Gabrielson


@JacobGabrielson: Kopya yapıldı. Asıl soru kopyanın yapılıp yapılmadığı ile ilgilidir.
abhinavkulkarni

3

Farklı süreçlerin farklı adres alanları vardır. Yorumlayıcının farklı örneklerini çalıştırmak gibi. IPC (süreçler arası iletişim) bunun içindir.

Bu amaçla kuyrukları veya boruları kullanabilirsiniz. İşlemleri daha sonra bir ağ üzerinden dağıtmak istiyorsanız, tcp üzerinden rpc de kullanabilirsiniz.

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


2
IPC'nin bunun için uygun olacağını düşünmüyorum; bu, herkesin erişmesi gereken salt okunur verilerdir. Süreçler arasında dolaşmanın anlamı yok; en kötü ihtimalle her biri kendi kopyasını okuyabilir. Her işlemde ayrı bir kopyaya sahip olmayarak bellekten tasarruf etmeye çalışıyorum.
Parand

Üzerinde çalışmak üzere veri parçalarını diğer ikincil işlemlere atayan bir ana işleminiz olabilir. Slave'ler veri isteyebilir veya veri aktarabilir. Bu şekilde, her işlem tüm nesnenin bir kopyasına sahip olmayacaktır.
Vasil

1
@Vasil: Ya her süreç tüm veri setine ihtiyaç duyuyorsa ve sadece üzerinde farklı bir işlem yürütüyorsa?
Will

1

Doğrudan çoklu işlemeyle ilgili değil, ancak sizin örneğinizden, sadece raf modülünü veya bunun gibi bir şeyi kullanabileceğiniz görülüyor . "Big_lookup_object" gerçekten tamamen bellekte olmak zorunda mı?


İyi bir nokta, disk üzerindeki performansla bellekteki performansı doğrudan karşılaştırmadım. Büyük bir fark olacağını düşünmüştüm ama aslında test etmedim.
Parand

1

Hayır, ancak verilerinizi alt süreç olarak yükleyebilir ve verilerini diğer çocuklarla paylaşmasına izin verebilirsiniz. aşağıya bakınız.

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)    

-4

Linux / Unix / MacOS platformu için forkmap hızlı ve kirli bir çözümdür.

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.