Clojure并行映射和无限序列

7
假设我按照以下方式定义所有自然数的序列:
(def naturals (iterate inc 0))

我也定义了一个将自然数映射到nil的函数,计算时间较长,代码如下:
(defn hard-comp [_] (Thread/sleep 500))

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

(dorun (map hard-comp (range 30))) ; 15010.367496毫秒

(dorun (pmap hard-comp (range 30))) ; 537.044554毫秒

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

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

(doall (take 30 naturals)) ; 0.385724毫秒

(range 30) ; 0.159374毫秒

当调用带有显式范围的pmap而不是naturals部分时,速度大约快了6倍。

由于(= (range 30) (take 30 naturals))返回true,并且两个对象都是类型为clojure.lang.LazySeq,并且Clojure在调用函数之前评估所有参数,则如何解释上述时间详细信息?

1个回答

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个项目。这是每个范围块抓取的元素数量。分块序列预先计算了一堆项以提高性能。在这种情况下,看起来pmap会在您尝试从范围中获取一个元素时立即生成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毫秒,其中4表示获取到100需要多少个32的块。)
另一方面,您可能不希望使用分块行为。32个线程一次很多。有关如何取消序列的分块化,请参见此问题中的示例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接