0

numpy を使用して一部のデータ展開を並列化しようとしていますが、並列化されたバージョンはシリアル バージョンよりも桁違いに時間がかかることがわかっているため、ばかげた間違いを犯しているに違いありません。

まず、問題を設定するためのいくつかの偽のデータ:

Ngroups = 1.e6
some_group_property = np.random.uniform(0, 100, Ngroups)
mem1_occupation = np.random.random_integers(0, 5, Ngroups)
mem2_occupation = np.random.random_integers(0, 5, Ngroups)
occupation_list = [mem1_occupation, mem2_occupation]

次に、シリアル計算について説明します。グループ データをグループ メンバーの配列に展開します。

mem1_property = np.repeat(some_group_property, mem1_occupation)
mem2_property = np.repeat(some_group_property, mem2_occupation)

パラレルバージョンは次のとおりです。

import functools
from joblib import Parallel, delayed

def expand_data(prop, occu_list, index):
    return np.repeat(prop, occu_list[index])
exp_data_1argfunc = functools.partial(expand_data, some_group_property, occupation_list)

result = Parallel(n_jobs=2)(delayed(exp_data_1argfunc)(i) for i in range(len(occupation_list)))  

私はこのコードを 4 コア マシンで実行しているので、原則として、2 つの母集団に対して個別に計算を実行すると、およそ 2 倍の速度が得られるはずです。代わりに、シリアル計算には約 0.1 秒かかり、パラレル計算には 9 秒かかります。ここで何が起こっているのですか?

4

1 に答える 1

2

初めに:

私はこのコードを 4 コア マシンで実行しているので、原則として、2 つの母集団に対して個別に計算を実行すると、およそ 2 倍の速度が得られるはずです。

いいえ。一般に、次のことを前提として、スレッド数に比例して速度が向上するのが絶対的な最良のシナリオです。

  1. コードの実行にかかる時間を決定する制限要因は、必要な CPU クロック サイクルの数です (つまり、ディスク I/O、メモリ割り当てなどではありません)。
  2. 並列化できないコードの部分はありません
  3. 並列化による追加のオーバーヘッドはありません

実際には、これらの基準が完全に満たされることはないため、直線的なスピードアップが可能であると自動的に想定すべきではありません。

functools.partialそうは言っても、中間関数宣言を取り除くことで、並列の例をかなり高速化できます。

def parallel_1(some_group_property, occupation_list):
    """ original version """
    exp_partial = functools.partial(expand_data, some_group_property,
                                    occupation_list)
    return Parallel(n_jobs=2)(
        delayed(exp_partial)(i)
        for i in range(len(occupation_list))
    )


def parallel_2(some_group_property, occupation_list):
    """ get rid of functools.partial """
    return Parallel(n_jobs=2)(
        delayed(expand_1)(some_group_property, occupation_list, i)
        for i in range(len(occupation_list))
    )


In [40]: %timeit parallel_1(some_group_property, occupation_list)
1 loops, best of 3: 7.24 s per loop

In [41]: %timeit parallel_2(some_group_property, occupation_list)
1 loops, best of 3: 375 ms per loop

Pythonmultiprocessingでは、関数とその引数はワーカー スレッドに送信される前に pickle 化され、そこで unpickle されて関数が実行されます。原因が正確にはわかりませんが、スローダウンはfunctools.partialオブジェクトのピクル化/アンピクル化がより困難になっていることに関係しているのではないかと思います。

また、すべてを含むリストではなく、特定のスレッドに必要な「占有」配列のみを渡すことで、少し改善することもできます。

def parallel_3(some_group_property, occupation_list):
    """ pass only a single occu array to each worker thread """
    return Parallel(n_jobs=2)(
        delayed(np.repeat)(some_group_property, oo)
        for oo in occupation_list
    )

In [44]: %timeit parallel_3(some_group_property, occupation_list)
1 loops, best of 3: 353 ms per loop

ただし、これはまだシングル スレッド バージョンのパフォーマンスに匹敵するほどではありません。

def serial_version(some_group_property, occupation_list):
    return [np.repeat(some_property_group, oo)
            for oo in occupation_list]

In [46]: %timeit serial_version(some_group_property, occupation_list)
10 loops, best of 3: 46.1 ms per loop

これはおそらく、並列化に伴う追加のオーバーヘッド (2 つのワーカー スレッドの開始、関数とその引数の pickle/unpick など) が、2 つの配列を並列に計算することによるパフォーマンスの向上を大幅に上回ることを意味します。

並列バージョンでは、ワーカー スレッドをセットアップして強制終了するのではなく、実際に有用な計算を実行する時間に比例して多くの時間が費やされる、はるかに大きな配列の並列化に何らかの利点が見られる可能性があると思います。

于 2015-01-20T02:14:44.713 に答える