web-dev-qa-db-ja.com

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

multiprocessing で生成された子プロセスは、プログラムで以前に作成されたオブジェクトを共有しますか?

次のセットアップがあります。

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

if __== '__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は共有ルックアップオブジェクトです。分割して個別に処理する必要はありません。単一のコピーを保持する必要があります。分割する必要がある作業は、他の多くの大きなファイルを読み取り、それらの大きなファイル内のアイテムをルックアップオブジェクトに対して検索することです。

更なる更新:データベースは素晴らしいソリューションであり、memcachedはより良いソリューションかもしれませんし、ディスク上のファイル(shelveまたはdbm)はさらに良いかもしれません。この質問では、特にメモリ内のソリューションに興味がありました。最終的なソリューションでは、hadoopを使用しますが、ローカルのメモリ内バージョンも使用できるかどうかを確認したかったのです。

96
Parand

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

いや.

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

ソリューション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を使用して各ページを見つけやすくします。

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

48
S.Lott

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

場合によります。グローバルな読み取り専用変数の場合は、(メモリの消費は別として)そうでないと見なされることがよくあります。

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 __== '__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
36
jfs

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

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

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

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

26
Jarret Hardie

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

import multiprocessing

x = 23

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

if __== '__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の各サブプロセスが使用している実メモリの量を確認します。

6

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

この目的には、キューまたはパイプを使用できます。後でネットワーク経由でプロセスを配布する場合は、tcp経由でrpcを使用することもできます。

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

2
Vasil

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

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)    
1
Mott The Tuple

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

1
Steven