9

単純な操作を大規模なシーケンスに並列に効率的に適用するために、clojure を使用する方法を理解しようとしています。並列ソリューションを使用して、マシンの複数のコアを利用して速度を向上させたいと考えています。

pmap を partition-all と組み合わせて使用​​して、入力シーケンス内のすべてのアイテムの未来を作成するオーバーヘッドを削減しようとしています。残念ながら、partition-all は各パーティション seq の完全な評価を強制します。これにより、マシンで OutOfMemoryError が発生します。

(defn sum [vs]
  (reduce + vs))

(def workers
  (+ 2 (.. Runtime getRuntime availableProcessors)))

(let
  [n 80000000
   vs (range n)]

  (time (sum vs))
  (time (sum (pmap sum (partition-all (long (/ n workers)) vs)))))

大規模な入力セットに合計を適用し、シリアル実装のパフォーマンスを上回るにはどうすればよいですか?

解決

レデューサー ライブラリを指摘してくれた @Arthur Ulfeldt に感謝します。レデューサーを使用したソリューションを次に示します。このコードは、マルチコア マシンで実行したときに予想されるパフォーマンスの向上を示しています。(注:タイミングをより正確にするための関数になるようにvsを変更しました)

(require '[clojure.core.reducers :as r])

(let
  [n 80000000
   vs #(range n)]

  (time (reduce + (vs)))
  (time (r/fold + (vs)))
4

1 に答える 1

9

pmap を使用すると、スイッチングと将来のオーバーヘッドを克服するためにかなり大きなチャンクが必要であることがわかりました+。潜在的な利益は、チャンクを生成するオーバーヘッドによって制限されます。これにより、使用可能なコアとチャンクの作成に必要な時間のバランスをとる最適な値が得られます。この場合+、ワークロードとしてシングル スレッド オプションよりも高速にすることはできませんでした。

pmap なしでこれを行い、潜在的に fork/join を使用することに興味がある場合は、新しい (ish) reducers ライブラリをチェックしてください

OOM の状況は、遅延シーケンスを実現する最初のテストから発生します。このテスト(range n)は保持されているため、2 番目のシーケンスに渡すことができます。

関数を定義して + 関数を大幅に遅くするslow+と、シングル スレッド、チャンク上の pmap、および forkJoin を使用するレデューサーの違いが明らかになります。

user> *clojure-version*                                                             
{:major 1, :minor 5, :incremental 0, :qualifier "RC15"}
(require '[clojure.core.reducers :as r]) 

(def workers
  (+ 2 (.. Runtime getRuntime availableProcessors)))

(defn slow+
  ([] 0)
  ([x] x)
  ([x y] (reduce + (range 100000)) (+ x y)))

(defn run-test []
  (let [n 8000]
   (time (reduce slow+ (range n)))
   (time (reduce slow+ (pmap #(reduce slow+ %) (partition-all (* workers 100) (range n)))))
   (time (r/fold slow+ (vec (range n)))))) 

user> (run-test)
"Elapsed time: 28655.951241 msecs" ; one thread
"Elapsed time: 6975.488591 msecs"  ; pmap over chunks
"Elapsed time: 8170.254426 msecs"  ; using reducer
于 2013-02-11T18:47:18.493 に答える