如何使用Clojure并行计算大量数字的总和

Joh*_*ood 9 parallel-processing clojure

我试图弄清楚如何使用clojure有效地将一个简单的操作并行地应用于一个大的序列.我希望能够使用并行解决方案来利用我机器上的多个内核来实现一些加速.

我试图将pmap与partition-all结合使用,以减少为输入seq中的每个项创建未来的开销.不幸的是,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)))))
Run Code Online (Sandbox Code Playgroud)

如何将sum应用于大型输入集,并优于串行实现的性能?

感谢@Arthur Ulfeldt指出reducers库.这是使用reducer的解决方案.此代码显示了在多核计算机上运行时预期的性能提升.(注意:我已将vs更改为一个函数,使时间更准确)

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

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

  (time (reduce + (vs)))
  (time (r/fold + (vs)))
Run Code Online (Sandbox Code Playgroud)

Art*_*ldt 9

当使用pmap时,我发现需要相当大的块来克服切换和未来的开销,尝试以大于10,000的块大小来进行操作+.潜在的收益受到生成块的开销的限制.这会产生一个最佳值,可以平衡可用内核和制作块所需的时间.在这种情况下,+作为工作负载,我无法使这比单线程选项更快.

如果您对没有pmap并且可能使用fork/join感兴趣,请查看新的(ish)reducers库

OOM情况来自第一次实现懒惰序列的测试(range n),然后保留该序列,以便将其传递给第二序列.

如果我通过定义一个slow+函数使+函数慢得多,并使用单线程,pmap over chunk和reducers w/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
Run Code Online (Sandbox Code Playgroud)