Clojure并行映射和无限序列

时间:2010-08-12 03:39:36

标签: performance optimization map clojure parallel-processing

假设我用以下方式定义所有自然数的序列:

(def naturals (iterate inc 0))

我还定义了一个将自然映射到nil的函数,这需要一段时间来计算:

(defn hard-comp [_] (Thread/sleep 500))

请注意计算时间以评估clojure.core/time所测量的以下s表达式。

(dorun (map hard-comp (range 30))) ; 15010.367496 msecs

(dorun (pmap hard-comp (range 30))) ; 537.044554 msecs

(dorun (map hard-comp (doall (take 30 naturals))))) ; 15009.488499 msecs

(dorun (pmap hard-comp (doall (take 30 naturals)))) ; 3004.499013 msecs

(doall (take 30 naturals)) ; 0.385724 msecs

(range 30); 0.159374毫秒

使用显式范围调用时,

pmap比使用自然部分快6倍。

由于(= (range 30) (take 30 naturals))返回true且两个对象都是clojure.lang.LazySeq类型,并且clojure在调用函数之前会调用函数的所有参数,如何解释上述时序细节?

1 个答案:

答案 0 :(得分:8)

我的猜测是因为这个原因:

user> (chunked-seq? (seq (range 30)))
true
user> (chunked-seq? (seq (take 30 naturals)))
false
user> (class (next (range 30)))
clojure.lang.ChunkedCons
user> (class (next (take 30 naturals)))
clojure.lang.Cons

试试这个:

user> (defn hard-comp [x] (println x) (Thread/sleep 500))
#'user/hard-comp
user> (time (dorun (pmap hard-comp (range 100))))

请注意,它一次跳转32个项目。这是一个范围内每个块抓取的元素数量。 Chunked seqs提前预先评估了一堆项目以提高性能。在这种情况下,只要你尝试从该范围中获取一个元素,它就会看起来像pmap chunkily产生32个线程。

你可以随时将自然填充到矢量中以获得分块行为。

user> (time (dorun (pmap hard-comp (range 100))))
"Elapsed time: 2004.680192 msecs"
user> (time (dorun (pmap hard-comp (vec (take 100 naturals)))))
"Elapsed time: 2005.887754 msecs"

(请注意,时间大约为4 x 500 ms,4是达到100所需的32个块的数量。)

另一方面,您可能不想要分块行为。一次32个线程很多。有关如何取消对seq进行分块的示例,请参阅this question