マルチプロセッシング:プロセス間で大きな読み取り専用オブジェクトを共有していますか?


107

子プロセスは、プログラムの前半で作成されたマルチプロセッシング共有オブジェクトを介して生成されますか?

次の設定があります。

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

いくつかの大きなオブジェクトをメモリに読み込んで、その大きなオブジェクトを利用する必要があるワーカーのプールを作成しています。ビッグオブジェクトは読み取り専用でアクセスされます。プロセス間で変更を渡す必要はありません。

私の質問です:unix / cでプロセスを生成した場合と同じように、ビッグオブジェクトは共有メモリに読み込まれますか、それとも各プロセスがビッグオブジェクトの独自のコピーを読み込みますか?

更新:さらに明確にするために-big_lookup_objectは共有ルックアップオブジェクトです。分割して個別に処理する必要はありません。コピーを1つだけ保持する必要があります。分割する必要がある作業は、他の多くの大きなファイルを読み取り、それらの大きなファイル内のアイテムをルックアップオブジェクトに対して検索することです。

さらなる更新:データベースは優れたソリューションであり、memcachedはより優れたソリューションであり、ディスク上のファイル(シェルブまたはdbm)はさらに優れている可能性があります。この質問では、メモリ内ソリューションに特に興味がありました。最終的な解決策として、私はhadoopを使用しますが、ローカルのメモリ内バージョンも使用できるかどうかを確認したいと思いました。


記述されたコードはmarshal.load、親と各子を呼び出します(各プロセスがモジュールをインポートします)。
jfs 2009年

正解です。
パランド

「ローカルインメモリ」の場合、および次のコピーを避けたい場合は、docs.python.org
library /…を

共有しない。生成されたプロセス(つまり、forkやexecなど)は、呼び出しプロセスの完全な複製ですが、別のメモリにあります。あるプロセスが別のプロセスと通信するには、プロセス間通信または共有メモリの場所へのIPC読み取り/書き込みが必要です。
ron

回答:


50

「子プロセスは、プログラムの前半で作成されたマルチプロセッシング共有オブジェクトを介して生成されますか?」

いいえ(3.8より前のpython)、3.8でははい(https://docs.python.org/3/library/multiprocessing.shared_memory.html#module-multiprocessing.shared_memory

プロセスには独立したメモリ空間があります。

解決策1

多くの労働者がいる大きな構造を最大限に活用するには、これを行います。

  1. 各ワーカーを「フィルター」として書き込みます– stdinから中間結果を読み取り、動作し、stdoutに中間結果を書き込みます。

  2. すべてのワーカーをパイプラインとして接続します。

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

各プロセスは読み取り、作業、書き込みを行います。

すべてのプロセスが同時に実行されているため、これは非常に効率的です。書き込みと読み取りは、プロセス間の共有バッファを直接通過します。


解決策2

場合によっては、より複雑な構造(多くの場合、「ファンアウト」構造)があります。この場合、複数の子を持つ親がいます。

  1. 親がソースデータを開きます。親は多くの子供たちをフォークします。

  2. 親はソースを読み取り、ソースの一部を同時に実行している各子にファームします。

  3. 親が最後に到達したら、パイプを閉じます。子はファイルの終わりを取得し、正常に終了します。

それぞれの子供が単に読むので、子供の部分は書くのが楽しいです sys.stdinです。

親は、すべての子を生成してパイプを適切に保持するために少し凝ったフットワークを持っていますが、それほど悪くはありません。

ファンインは反対の構造です。独立して実行されている多くのプロセスは、入力を共通のプロセスにインターリーブする必要があります。コレクターは多くのソースから読み取る必要があるため、書くのは簡単ではありません。

多くの名前付きパイプからの読み取りは、多くの場合、selectモジュールを使用して行われ、どのパイプに保留中の入力があるかを確認します。


解決策3

共有ルックアップは、データベースの定義です。

ソリューション3A –データベースをロードします。ワーカーがデータベース内のデータを処理できるようにします。

ソリューション3B – werkzeug(または類似の)を使用して非常に単純なサーバーを作成し、HTTP GETに応答するWSGIアプリケーションを提供して、ワーカーがサーバーにクエリを実行できるようにします。


解決策4

共有ファイルシステムオブジェクト。Unix OSは共有メモリオブジェクトを提供します。これらは単なるメモリにマップされたファイルなので、従来のバッファリングされた読み取りの代わりにI / Oのスワップが行われます。

あなたはいくつかの方法でPythonコンテキストからこれを行うことができます

  1. (1)元の巨大なオブジェクトをより小さなオブジェクトに分解し、(2)それぞれがより小さなオブジェクトでワーカーを開始するスタートアッププログラムを記述します。小さいオブジェクトをピクルスにしたPythonオブジェクトにすると、ファイルの読み取り時間をわずかに節約できます。

  2. (1)元の巨大なオブジェクトを読み取り、seek操作を使用してページ構造のバイトコードファイルを書き込むスタートアッププログラムを記述し、単純なシークで個々のセクションを簡単に見つけられるようにします。これがデータベースエンジンの機能です。データをページに分割し、各ページをseek

    この大きなページ構造のファイルにアクセスできるスポーンワーカー。各労働者は、関連する部分を探し、そこで作業することができます。


私のプロセスは実際には適合者ではありません。それらはすべて同じで、異なるデータを処理するだけです。
パランド

多くの場合、フィルターとして構成できます。彼らはデータを読み取り、作業を行い、結果を後で処理するために書き込みます。
S.Lott、2009年

私はあなたのソリューションが好きですが、ブロッキングI / Oはどうなりますか?親がその子の1つに対する読み書きをブロックした場合はどうなりますか?Selectは書き込み可能であることを通知しますが、どの程度かは示していません。読書についても同じです。
クリスティアンCiupitu 2009年

これらは別個のプロセスです。親と子は互いに干渉しません。パイプの一方の端で生成された各バイトは、もう一方の端ですぐに使用できます。パイプは共有バッファーです。この文脈であなたの質問が何を意味するのかわからない。
S.Lott、2009年

S.Lottが言ったことを確認できます。同じ操作を1つのファイルで行う必要がありました。したがって、最初のワーカーは%2 == 0のすべての行で関数を実行してファイルに保存し、他の行を次のパイプ処理されたプロセス(同じスクリプト)に送信しました。ランタイムが半分になりました。少しハックですが、オーバーヘッドはマルチプロセッシングモジュールのmap / poopよりもはるかに軽量です。
ビンス

36

子プロセスは、プログラムの前半で作成されマルチプロセッシング共有オブジェクトを介して生成されますか?

場合によります。グローバルな読み取り専用変数の場合、それは(消費されたメモリを除いて)そう考えられることが多く、そうでない場合は考慮されません。

multiprocessingのドキュメントは言う:

Better to inherit than pickle/unpickle

Windowsでは、子プロセスがそれらを使用できるように、マルチプロセッシングの多くのタイプをピクル可能にする必要があります。ただし、一般に、パイプまたはキューを使用して共有オブジェクトを他のプロセスに送信することは避けてください。代わりに、他の場所で作成された共有リソースへのアクセスを必要とするプロセスが祖先プロセスからそれを継承できるようにプログラムを調整する必要があります。

Explicitly pass resources to child processes

Unixでは、子プロセスは、グローバルリソースを使用して親プロセスで作成された共有リソースを利用できます。ただし、オブジェクトを引数として子プロセスのコンストラクターに渡すことをお勧めします。

コードを(可能性として)Windowsと互換性を持たせることとは別に、これにより、子プロセスがまだ生きている限り、オブジェクトが親プロセスでガベージコレクションされないことも保証されます。これは、オブジェクトが親プロセスでガベージコレクションされているときにリソースが解放される場合に重要になることがあります。

Global variables

子プロセスで実行されているコードがグローバル変数にアクセスしようとすると、その値(ある場合)は、Process.start()が呼び出されたときの親プロセスの値と同じではない可能性があることに注意してください。 。

Windows(シングル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))

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

なし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
えっ?プロセス間でzはどのように共有されますか?
cbare

4
@cbare:いい質問です!スリープの出力が示すように、zは実際には共有されません。スリープなしの出力は、単一のプロセスがすべての作業を処理する(PID = 1148)ことを示しています。最後の例で見られるのは、この単一プロセスのzの値です。
エリックOレビゴット2012

この回答は、それzが共有されていないことを示しています。したがって、これは「いいえ、少なくともWindowsでは、親変数は子の間で共有されません」という質問に答えます。
Eric O Lebigot 2017年

@EOL:技術的には正しいですが、実際にはデータが読み取り専用のz場合(ケースとは異なり)、共有されていると見なすことができます。
jfs 2017年

明確にするために、ステートメントは、子プロセスで実行されるコードがグローバル変数にアクセスしようとすると、2.7のドキュメントではWindowsで実行されているPythonを指すことに注意してください。
user1071847 2017年

28

S.Lottは正しいです。Pythonのマルチプロセッシングショートカットは、個別の重複したメモリのチャンクを効果的に提供します。

ほとんどの* nixシステムでは、低レベルの呼び出しを使用して os.fork()すると、実際にはコピーオンライトメモリが提供されますが、これはあなたが考えていることかもしれません。AFAIKは、理論的には、可能な限り最も単純なプログラムでは、複製せずにそのデータから読み取ることができます。

しかし、Pythonインタープリターでは物事はそれほど単純ではありません。オブジェクトデータとメタデータは同じメモリセグメントに保存されるため、オブジェクトが変更されない場合でも、インクリメントされるそのオブジェクトの参照カウンタのようなものがメモリへの書き込みを引き起こし、したがってコピーを引き起こします。「print 'hello'」以外の処理を行うほとんどすべてのPythonプログラムは、参照カウントの増加を引き起こすため、コピーオンライトの利点を実現することはおそらくありません。

誰かがPythonで共有メモリソリューションをハッキングできたとしても、プロセス間でガベージコレクションを調整しようとすると、おそらくかなり骨が折れるでしょう。


3
その場合、参照カウントのメモリ領域のみがコピーされ、必ずしも大きな読み取り専用データではありませんか?
kawing-chiu 2016

7

Unixで実行している場合、フォークのしくみにより、同じオブジェクトを共有する可能性があります(つまり、子プロセスには個別のメモリがありますが、コピーオンライトであるため、誰も変更しない限り共有できます)。私は以下を試しました:

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

そして、次の出力を得ました:

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

もちろん、これはコピーが作成されなかったこと証明するものではありませんが、ps各サブプロセスが使用している実メモリの量を確認するためにの出力を確認することで、状況を確認できるはずです。


2
ガベージコレクターはどうですか?実行するとどうなりますか?メモリレイアウトは変わりませんか?
クリスティアンCiupitu 2009年

それは正当な懸念事項です。それがパランドに影響を与えるかどうかは、彼がこれらすべてをどのように使用しているか、このコードの信頼性に依存するでしょう。彼にとってうまくいかなかった場合は、より詳細な制御のためにmmapモジュールを使用することをお勧めします(彼がこの基本的なアプローチに固執したい場合)
Jacob Gabrielson

私はあなたの例にアップデートを掲載しました:stackoverflow.com/questions/659865/...は
JFS

@JacobGabrielson:コピーが作成されます。元の質問は、コピーが作成されるかどうかです。
abhinavkulkarni 2013

3

プロセスによってアドレス空間は異なります。インタプリタの異なるインスタンスを実行するように。それがIPC(プロセス間通信)の目的です。

この目的のために、キューまたはパイプを使用できます。後でネットワークを介してプロセスを分散する場合は、rpc over tcpを使用することもできます。

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


2
IPCがこれに適しているとは思いません。これは、誰もがアクセスする必要がある読み取り専用データです。プロセス間でそれを渡す意味はありません。最悪の場合、それぞれが独自のコピーを読み取ることができます。プロセスごとに個別のコピーを作成しないことで、メモリを節約しようとしています。
パランド

データの一部を他のスレーブプロセスに委任するマスタープロセスを持つことができます。スレーブはデータを要求することも、データをプッシュすることもできます。この方法では、すべてのプロセスがオブジェクト全体のコピーを持つわけではありません。
Vasil

1
@Vasil:各プロセスがデータセット全体を必要とし、それに対して異なる操作を実行している場合はどうなりますか?
2013年

1

それ自体はマルチプロセッシング自体に直接関係しているわけではありませんが、あなたの例では、シェルフモジュールなどを使用するだけでよいように思えます。「big_lookup_object」は本当に完全にメモリ内にある必要がありますか?


良い点は、ディスク上のパフォーマンスとメモリ内のパフォーマンスを直接比較していないことです。大きな違いがあると思っていましたが、実際にはテストしていません。
パランド

1

いいえ。ただし、データを子プロセスとしてロードして、他の子とデータを共有することができます。下記参照。

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)    

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.