我试图弄清楚如何使用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)))))
如何将sum应用于大型输入集,并超越串行实现的性能?
解决方案
感谢@Arthur Ulfeldt指出redurs库。这是使用reducer的解决方案。此代码显示了在多核计算机上运行时预期的性能提升。 (注意:我已经改变vs作为使时间更准确的函数)
(require '[clojure.core.reducers :as r])
(let
[n 80000000
vs #(range n)]
(time (reduce + (vs)))
(time (r/fold + (vs)))
答案 0 :(得分:9)
当使用pmap时,我发现需要相当大的块来克服切换和未来的开销尝试一个大小为10,000的块大小,以及+
的快速操作。潜在的收益受到生成块的开销的限制。这会产生一个最佳值,可以平衡可用内核和制作块所需的时间。在这种情况下,+
作为工作负载,我无法使其比单线程选项更快。
如果您对没有pmap并且可能使用fork / join感兴趣,请查看新的(ish)reducers library
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