调整Clojure Reducers Library性能
题
为什么使用减速器映射/减少库的性能比普通地图/减少更差?
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)
.
我有两次杀死后来,因为它无限期地需要。这里有什么问题?
编辑: 似乎其他语言也有类似的问题。Scala似乎只爆发了一百万。 Scala并行集合有时会导致OutofMemoryError?。虽然Clojure Reducers比百万的正常水平更快。
解决方案
补充@ a-webb的答案,这是一个编译器错误,涉及一个真正修复。(有关更多详细信息,请参阅此帖子。)
解决问题的另一种方法是使用 fuse :
(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
. 其他提示
性能磨削以停止,因为内存被耗尽。如果您继续等待,您很可能会遇到内存错误。创建减速器将收集的头部保持在关闭中。因此,巨大的惰性序列在实现时结束内存。
这是发生的事情,蒸馏出来
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 有一个 nofoling 如何以懒惰的方式撰写减速器。
Reducers与懒惰列表实际上并没有很好地运行,而正常减少则延迟。
为了从减速器中获得真正的好处,您需要一个非懒惰的集合,例如,向量,您需要使用折叠而不是减少。
(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"
.
减速器与Fork / Join Framework一起工作,需要大块数据。在懒惰(块)序列中,您没有这些大块,所以fork /加入无法正常工作。
在解释概念的减速器上有富豪的富豪谈话真的很好: https://vimeo.com/45561411
不隶属于 StackOverflow