14

次の関数を検討してください。

def f(x, dummy=list(range(10000000))):
    return x

を使用するmultiprocessing.Pool.imapと、次のタイミングが得られます。

import time
import os
from multiprocessing import Pool

def f(x, dummy=list(range(10000000))):
    return x

start = time.time()
pool = Pool(2)
for x in pool.imap(f, range(10)):
    print("parent process, x=%s, elapsed=%s" % (x, int(time.time() - start)))

parent process, x=0, elapsed=0
parent process, x=1, elapsed=0
parent process, x=2, elapsed=0
parent process, x=3, elapsed=0
parent process, x=4, elapsed=0
parent process, x=5, elapsed=0
parent process, x=6, elapsed=0
parent process, x=7, elapsed=0
parent process, x=8, elapsed=0
parent process, x=9, elapsed=0

functools.partialデフォルト値を使用する代わりに使用すると、次のようになります。

import time
import os
from multiprocessing import Pool
from functools import partial

def f(x, dummy):
    return x

start = time.time()
g = partial(f, dummy=list(range(10000000)))
pool = Pool(2)
for x in pool.imap(g, range(10)):
    print("parent process, x=%s, elapsed=%s" % (x, int(time.time() - start)))

parent process, x=0, elapsed=1
parent process, x=1, elapsed=2
parent process, x=2, elapsed=5
parent process, x=3, elapsed=7
parent process, x=4, elapsed=8
parent process, x=5, elapsed=9
parent process, x=6, elapsed=10
parent process, x=7, elapsed=10
parent process, x=8, elapsed=11
parent process, x=9, elapsed=11

バージョンの使用functools.partialが非常に遅いのはなぜですか?

4

1 に答える 1

15

を使用multiprocessingするには、渡す引数だけでなく、実行する関数に関する情報をワーカー プロセスに送信する必要があります。その情報は、メイン プロセスでその情報を pickle 化し、ワーカー プロセスに送信し、そこで unpickle することによって転送されます。

これにより、主な問題が発生します。

デフォルトの引数で関数をピクルするのは安上がりです。関数の名前をピクルするだけです(さらに、Pythonに関数であることを知らせるための情報)。ワーカー プロセスは名前のローカル コピーを検索するだけです。彼らはすでにf見つけるべき名前付き関数を持っているので、それを渡すのにほとんど費用はかかりません。

しかしpartial関数をピクルすることは、基になる関数 (安価) とすべてのデフォルト引数 (デフォルト引数が 10M の長さの場合は高価list)をピクルすることを含みます。そのため、ケースでタスクがディスパッチされるたびにpartial、バインドされた引数が pickle 化され、それがワーカー プロセスに送信され、ワー​​カー プロセスが unpickle され、最後に「実際の」作業が行われます。私のマシンでは、その pickle のサイズは約 50 MB であり、これは膨大な量のオーバーヘッドです。list私のマシンでの簡単なタイミング テストでは、1,000 万の長さの酸洗と酸洗解除に0約 620 ミリ秒かかります (これは、実際に 50 MB のデータを転送するオーバーヘッドを無視しています)。

partial彼らは自分の名前を知らないので、このようにピクルスにする必要があります。のような関数をピクルするとき( -ed である) は、その修飾名を (対話型インタープリターで、またはプログラムのメイン モジュールから) 知っているfので、リモート側は と同等のことを行うことでローカルにそれを再作成することができます。しかし、はその名前を知りません。確かに、あなたはそれをに割り当てましたが、それ自体も修飾名で利用できることも知りません。名前を付けたり、他の何百万ものものにすることができます。したがって、ゼロから完全に再作成するために必要な情報が必要です。もfdef__main__.ffrom __main__ import fpartialgpicklepartial__main__.gfoo.fredpicklepickle-呼び出し可能オブジェクトがワークアイテム間で親で変更されていないことを認識せず、常に最新の状態に送信されるようにしようとしているため、呼び出しごとに (ワーカーごとに 1 回だけでなく) ing します。

list他にも問題があります (その場合のみの作成のタイミングと、ラップされた関数を呼び出す場合と関数を直接呼び出すpartial場合のわずかなオーバーヘッド) 。の最初の作成では、pickle/unpickle サイクルのコストの半分弱の 1 回限りのオーバーヘッドが追加されます ( を介して呼び出すオーバーヘッドは 1 マイクロ秒未満です)。partialpartiallistpartial

于 2016-01-28T13:14:12.867 に答える