在Reducers的许多资源中(如the canonical blog post by Rich Hickey),它声称reducers比常规集合函数((map ... (filter ...))
等)更快,因为开销更少。
避免了多少额外开销? IIUC即使是懒惰的集合函数最终只能执行原始序列 。计算中间结果的细节有何不同?
指示Clojure实施中有助于理解差异的相关位置也是最有帮助的
答案 0 :(得分:5)
“请注意,没有生成中间集合。”
这是改进,因为例如对垃圾收集器的压力较小。
当您组合map
,filter
等操作时,这些函数会迭代一个集合并返回一个新集合,然后将其传递给下一个函数。减速器不是这种情况。
因此它们可以用作那些函数(也就是说,它们以相同的方式组成)并且可以应用于相同的集合。但随着性能的提升。
有或没有减速器的地图/地图/过滤器操作的粗略且完全不科学的草图如下:
没有减速机
初始收集=> map =>中介收集=> map => 中介收集=> filter =>最终收藏
没有reducer(即clojure.core map / filter函数),懒惰地生成中间集合。 也就是说,新元素仅在下一个处理步骤需要时生成,一次一个元素或一个块。
注意:块是一个包含32个元素的块(尽管这应该被视为实现细节)。这是出于效率的原因,以避免从一个计算“跳跃”到另一个计算。
例如,请参阅map
函数return value:它返回lazy-seq
(transducers除外)。
使用缩减器
初始收集=> map / map / filter reducer =>最终收藏
如果你需要快速处理整个系列,那么懒惰就会影响性能。删除惰性seq的中间层可提供性能提升。 Reducers执行此操作并热切地处理集合,因此更快。您还可以使用相同的功能来简化切换。
请同时阅读以下评论,特别是来自@MichałMarczyk的评论
答案 1 :(得分:3)
我认为一个关键的见解来自original blog post:
的以下段落(require '[clojure.core.reducers :as r]) (reduce + (r/filter even? (r/map inc [1 1 1 2]))) ;=> 6
这应该看起来很熟悉 - 它是相同的命名函数,以相同的顺序应用,具有相同的参数,产生与Clojure的基于seq的fns相同的结果。不同之处在于,减少急切,并且这些减速器fns不在seq游戏中,没有每步分配开销,因此速度更快。懒惰在你需要的时候很棒,但是当你没有时,你不应该为此付出代价。
延迟序列的实现伴随着(线性)分配成本:每次实现lazy seq中的另一个元素时,seq的 rest 存储在一个新的thunk中,并且这种'thunk'的表示是a new clojure.lang.LazySeq
object。
我相信那些LazySeq
个对象是引用中引用的分配开销。对于reducers,没有逐渐实现lazy seq元素,因此根本没有LazySeq
thunk的实例化。