调整clojure reducers库性能

时间:2014-02-26 04:35:19

标签: clojure reducers

为什么使用reducers库进行映射/缩减的性能比普通map / reduce更差?

user=> (time (reduce + (map inc (range 100000000))))
"Elapsed time: 14412.627966 msecs"
5000000050000000

user=> (time (reduce + (r/map inc (range 100000000))))
... (C-c)

user=> (time (r/reduce + (r/map inc (range 100000000))))
....(C-c)

我有两次杀死后两次,因为它无限期地长。这有什么不对?

修改 似乎其他语言也有类似的问题。斯卡拉似乎只打破了一百万。 Why do Scala parallel collections sometimes cause an OutOfMemoryError?。虽然clojure减速器的速度比正常速度快了一百万。

3 个答案:

答案 0 :(得分:6)

为了补充@ a-webb的答案,这是一个编译器错误,并且涉及一个真正修复。 (See this post for more details.

解决此问题的另一种方法是使用保险丝

(defn once-seq
  "Returns a sequence on which seq can be called only once."
  [coll]
  (let [a (atom coll)]
    (reify clojure.lang.Seqable
      (seq [_]
        (let [coll @a]
          (reset! a nil)
          (seq coll))))))

然后:

=> (time (r/reduce + (r/map inc (once-seq (range 100000000)))))
"Elapsed time: 3692.765 msecs"
5000000050000000

答案 1 :(得分:2)

由于内存耗尽,性能正在停滞不前。如果你一直等待,你很可能会遇到内存错误。创建一个reducer将集合的头部保存在一个闭包中。因此,巨大的懒惰序列会在记忆变得紧密时占据记忆。

这是发生了什么,蒸馏

user=> (def n 100000000)
#'user/n

user=> (time (dorun (range n)))
"Elapsed time: 12349.034702 msecs"

现在一样,但是来自一个闭包

user=> (defn foo [x] (fn [f] (f x)))
#'user/foo

user=> (time ((foo (range n)) dorun))
OutOfMemoryError GC overhead limit exceeded ... (sometime later)

比较
(time (do (doall (range n)) nil))
OutOfMemoryError GC overhead limit exceeded ... (sometime later)

减少者中的嫌疑人关闭

user=> (source r/folder)
(defn folder
  "Given a foldable collection, [...]"
  {:added "1.5"}
  ([coll xf]
     (reify
      clojure.core.protocols/CollReduce
      (coll-reduce [_ f1]
                   (clojure.core.protocols/coll-reduce coll (xf f1) (f1)))
   ...

Christophe Grand有一个nice post关于如何以懒惰的方式构建缩减器。

答案 2 :(得分:1)

对于惰性列表,减速器并不能很好地工作,而正常的减少会有效。

要从减速器中获得真正的好处,您需要一个非惰性的集合,例如:一个向量,你需要使用fold而不是reduce。

 (def v (into [] (range 10000000)))
 (time (reduce + (map inc v)))
 ;; "Elapsed time: 896.79 msecs"
 (time (reduce + (r/map inc v)))
 ;; "Elapsed time: 671.947 msecs" 
 (time (r/fold + (r/map inc v)))
 ;; "Elapsed time: 229.45 msecs"

Reducers使用fork / join Framework,它需要大量的数据块。在一个懒惰(分块)序列中,你没有这些大块,所以fork / join不能正常工作。

Rich Hickey就减速器进行了一次演讲,可以很好地解释这些概念:https://vimeo.com/45561411