web-dev-qa-db-ja.com

python-複数のプロセス間でラージオブジェクト(pandas dataframe)など)を共有するマルチプロセッシング

Python multiprocessing、より正確に

from multiprocessing import Pool
p = Pool(15)

args = [(df, config1), (df, config2), ...] #list of args - df is the same object in each Tuple
res = p.map_async(func, args) #func is some arbitrary function
p.close()
p.join()

このアプローチでは、大量のメモリが消費されます。私のRAM=(この時点で非常に遅くなるため、マルチプロセッシングがかなり役に立たなくなります)をほぼすべて使い果たします。問題は、dfが巨大なオブジェクト(大きなpandas dataframe)であり、プロセスごとにコピーされます。multiprocessing.Valueを使用して、コピーせずにデータフレームを共有しようとしました

shared_df = multiprocessing.Value(pandas.DataFrame, df)
args = [(shared_df, config1), (shared_df, config2), ...] 

Pythonマルチプロセッシング共有メモリ で提案されているように)しかし、これによりTypeError: this type has no sizeSharingと同じPython processes? の間の複雑なオブジェクト、これについては残念ながら答えがわかりません)。

私は初めてマルチプロセッシングを使用していますが、おそらく私の理解は(まだ)十分ではありません。この場合、multiprocessing.Valueは実際に使用するのにも適切ですか?他の提案(キューなど)を見てきましたが、今では少し混乱しています。メモリを共有するにはどのようなオプションがありますか?この場合、どのオプションが最適ですか?

41
Anne

Valueの最初の引数はtypecode_or_typeです。それは次のように定義されます:

typecode_or_typeは、返されるオブジェクトのタイプを決定します:これは、ctypesタイプまたは配列モジュールで使用される種類の1文字のタイプコードです* argsは型のコンストラクターに渡されます。

鉱山を強調します。したがって、単にpandasデータフレームをValueに入れることはできません。 ctypesタイプ でなければなりません。

代わりにmultiprocessing.Managerシングルトンデータフレームインスタンスをすべてのプロセスに提供します。同じ場所に到達するにはいくつかの異なる方法があります-おそらく最も簡単な方法は、データフレームをマネージャーのNamespaceに配置することです。

from multiprocessing import Manager

mgr = Manager()
ns = mgr.Namespace()
ns.df = my_dataframe

# now just give your processes access to ns, i.e. most simply
# p = Process(target=worker, args=(ns, work_unit))

これで、Managerへの参照が渡されるすべてのプロセスからデータフレームインスタンスにアクセスできます。または、Namespaceへの参照を渡すだけで、よりクリーンになります。

私がカバーしなかった/カバーしなかったことの1つは、イベントとシグナリングです。プロセスが他のプロセスの実行が完了するのを待つ必要がある場合は、それを追加する必要があります。 ページはこちら いくつかEventの例では、マネージャーのNamespaceの使用方法についても詳しく説明しています。

(これは、multiprocessingが明確なパフォーマンス上の利点をもたらすかどうかに対処していないことに注意してください。これは、その質問を調査するためのツールを提供するだけです)

35
roippi

Data_handler子プロセスを作成することにより、メモリオーバーヘッドなしでプロセス間でpandasデータフレームを共有できます。このプロセスは、特定のデータ要求(つまり、行、特定のセル、スライスなど)。非常に大きなデータフレームオブジェクトから。データフレームをすべての子プロセスにコピーする名前空間のようなManagerとは異なり、data_handlerプロセスのみがデータフレームをメモリに保持します。実際の例については、以下を参照してください。プール。

これにはプログレスバーが必要ですか?ここで私の答えを参照してください: https://stackoverflow.com/a/55305714/11186769

import time
import Queue
import numpy as np
import pandas as pd
import multiprocessing
from random import randint

#==========================================================
# DATA HANDLER
#==========================================================

def data_handler( queue_c, queue_r, queue_d, n_processes ):

    # Create a big dataframe
    big_df = pd.DataFrame(np.random.randint(
        0,100,size=(100, 4)), columns=list('ABCD'))

    # Handle data requests
    finished = 0
    while finished < n_processes:

        try:
            # Get the index we sent in
            idx = queue_c.get(False)

        except Queue.Empty:
            continue
        else:
            if idx == 'finished':
                finished += 1
            else:
                try:
                    # Use the big_df here!
                    B_data = big_df.loc[ idx, 'B' ]

                    # Send back some data
                    queue_r.put(B_data)
                except:
                    pass    

# big_df may need to be deleted at the end. 
#import gc; del big_df; gc.collect()

#==========================================================
# PROCESS DATA
#==========================================================

def process_data( queue_c, queue_r, queue_d):

    data = []

    # Save computer memory with a generator
    generator = ( randint(0,x) for x in range(100) )

    for g in generator:

        """
        Lets make a request by sending
        in the index of the data we want. 
        Keep in mind you may receive another 
        child processes return call, which is
        fine if order isnt important.
        """

        #print(g)

        # Send an index value
        queue_c.put(g)

        # Handle the return call
        while True:
            try:
                return_call = queue_r.get(False)
            except Queue.Empty:
                continue
            else:
                data.append(return_call)
                break

    queue_c.put('finished')
    queue_d.put(data)   

#==========================================================
# START MULTIPROCESSING
#==========================================================

def multiprocess( n_processes ):

    combined  = []
    processes = []

    # Create queues
    queue_data = multiprocessing.Queue()
    queue_call = multiprocessing.Queue()
    queue_receive = multiprocessing.Queue()

    for process in range(n_processes): 

        if process == 0:

                # Load your data_handler once here
                p = multiprocessing.Process(target = data_handler,
                args=(queue_call, queue_receive, queue_data, n_processes))
                processes.append(p)
                p.start()

        p = multiprocessing.Process(target = process_data,
        args=(queue_call, queue_receive, queue_data))
        processes.append(p)
        p.start()

    for i in range(n_processes):
        data_list = queue_data.get()    
        combined += data_list

    for p in processes:
        p.join()    

    # Your B values
    print(combined)


if __name__ == "__main__":

    multiprocess( n_processes = 4 )
1
Mott The Tuple