7

以下に示すように、cojure REPL で clojure 関数マップと pmap をテストしました。それは私を混乱させます: なぜ並列 pmap は map よりも遅いのですか?

user=> (def lg (range 1 10000000))
user=> (time (def rs (doall (pmap #(* % %) lg))))

"Elapsed time: **125739.056** msecs"

# -------------------------------------------------------
user=> (def lg (range 1 10000000))
user=> (time (def rs (doall (map #(* % %) lg))))

"Elapsed time: **5804.485** msecs"

**PS: the machine has 8 cores**
4

3 に答える 3

18

すべての並列処理タスクには、タスクの調整によるオーバーヘッドがいくらかあります。 pmap別のスレッドで各要素に個別にマッピング関数を適用します。によって返された遅延シーケンスpmapが消費されると、消費者スレッドは生産者スレッドと調整する必要があります。方法pmapが定義されており、このオーバーヘッドは、生成されるすべての要素に対して発生します。

これを考慮pmapすると、単純な関数 (例のように数値を 2 乗するなど) を計算するために使用する場合、スレッドがアクティビティを調整するのにかかる時間は、実際に値を計算するのにかかる時間を圧倒します。docstring が言うように、 「 f の時間が調整のオーバーヘッドを支配する計算集約型pmapの関数にのみ役立ちます」(エンパシスが追加されています)。このような場合、コアの数に関係なく、より時間がかかります。pmapmap

から実際に利益を得るにはpmap、「より難しい」問題を選択する必要があります。場合によっては、これは入力シーケンスをチャンクに分割するのと同じくらい簡単かもしれません。次に、チャンクのシーケンスを処理しpmapて実行しconcat、最終的な出力を得ることができます。

例えば:

(defn chunked-pmap [f partition-size coll]
  (->> coll                           ; Start with original collection.

       (partition-all partition-size) ; Partition it into chunks.

       (pmap (comp doall              ; Map f over each chunk,
                   (partial map f)))  ; and use doall to force it to be
                                      ; realized in the worker thread.

       (apply concat)))               ; Concatenate the chunked results
                                      ; to form the return value.

ただし、シーケンスを分割し、最後にチャンクを連結するためのオーバーヘッドもあります。たとえば、少なくとも私のマシンでは、あなたの例ではchunked-pmapまだmapかなりのパフォーマンスが低下しています。それでも、一部の機能では有効な場合があります。

の有効性を向上させるもう 1 つの方法pmapは、アルゴリズム全体の別の場所で作業を分割することです。たとえば、点のペア間のユークリッド距離の計算に関心があるとします。二乗関数の並列化は効果がないことが証明されていますが、距離関数全体を並列化することで運が良くなるかもしれません。現実的には、タスクをさらに高いレベルで分割したいと考えていますが、それが要点です。

つまり、並列アルゴリズムのパフォーマンスは、タスクが分割される方法に敏感であり、テストには細かすぎるレベルを選択しました。

于 2013-11-14T08:02:30.287 に答える
3

Rörd の言うとおりです。pmap を使用すると、かなりのオーバーヘッドが発生します。代わりにレデューサーの使用を検討してください。

(def l (range 10000000))

(time (def a (doall (pmap #(* % %) l))))
"Elapsed time: 14674.415781 msecs"

(time (def a (doall (map #(* % %) l))))
"Elapsed time: 1119.107447 msecs"

(time (def a (doall (into [] (r/map #(* % %) l)))))
"Elapsed time: 1049.754652 msecs"
于 2013-11-13T14:26:12.093 に答える
2

スレッドを作成し、それらの間でワークロードを分割し、結果を再構築するには、いくらかのオーバーヘッドがあります。#(* % %)速度の向上を確認するよりも大幅に長く実行される関数が必要になりますpmap(もちろん、質問で指定しなかった CPU のコア数にも依存します)。

于 2013-11-13T12:10:10.017 に答える