为什么Clojure';s core.reducer比惰性集合函数更快

为什么Clojure';s core.reducer比惰性集合函数更快,clojure,functional-programming,reducers,Clojure,Functional Programming,Reducers,在许多关于reducer(如)的资源中,有人声称reducer比常规收集函数更快((map…(filter…)等等),因为开销更小 避免的额外开销是什么?IIUC即使是惰性集合函数也只会遍历原始序列一次。中间结果的计算细节是否存在差异 指向Clojure实现中帮助理解差异的相关位置的指针也将非常有用 “注意,没有生成中间集合。” 这就是改进之处,例如,对垃圾收集器的压力更小 当您组合诸如map、filter之类的操作时,这些函数将迭代一个集合并返回一个新集合,然后将该集合传递给下一个函数。异径管

在许多关于reducer(如)的资源中,有人声称reducer比常规收集函数更快(
(map…(filter…)
等等),因为开销更小

避免的额外开销是什么?IIUC即使是惰性集合函数也只会遍历原始序列一次。中间结果的计算细节是否存在差异

指向Clojure实现中帮助理解差异的相关位置的指针也将非常有用

“注意,没有生成中间集合。”

这就是改进之处,例如,对垃圾收集器的压力更小

当您组合诸如
map
filter
之类的操作时,这些函数将迭代一个集合并返回一个新集合,然后将该集合传递给下一个函数。异径管的情况并非如此

因此,它们可以像那些函数一样使用(也就是说,它们以相同的方式组合),并且可以应用于相同的集合。但随着性能的提高

有或没有减缩器的映射/映射/过滤操作的粗略且完全不科学的草图如下所示:

不带减速器

初始集合=>map=>intermediate集合=>map=> 中间集合=>筛选器=>最终集合

如果没有减缩器(即clojure.core映射/过滤器函数),中间集合将延迟生成。 也就是说,只有在下一个处理步骤需要时才生成新元素,一次生成一个元素或一个块

注意:块是由32个元素组成的块(尽管这应该被视为一个实现细节)。这是为了提高效率,避免从一个计算“跳”到另一个计算

例如,请参见
map
函数:它返回一个值(在以下情况下除外)

带减速器

初始集合=>map/map/filter reducer=>final集合

如果您需要快速处理整个集合,那么懒惰会影响性能。删除惰性seq的中间层可以提高性能。减缩器执行此操作并热切地处理收集,因此速度更快。您还可以保持相同的功能以简化切换


还请阅读下面的评论,特别是@MichałMarczyk的评论,我认为下面这段话是一个关键的见解:

这看起来应该很熟悉——它是相同的命名函数,以相同的顺序应用,具有相同的参数,产生与Clojure基于seq的fns相同的结果。不同的是,reduce是渴望的,而这些reducer fns是退出seq游戏的,没有每一步的分配开销,因此速度更快。懒惰在你需要的时候是伟大的,但当你不需要的时候,你不应该为此付出代价

惰性序列的实现伴随着(线性)分配成本:每次实现惰性序列中的另一个元素时,序列的其余部分存储在一个新的thunk中,并且这种“thunk”的表示形式为


我相信那些
LazySeq
对象就是报价中提到的分配开销。使用reducer时,惰性seq元素没有逐渐实现,因此根本没有
LazySeq
thunks的实例化。

我不相信这是真的。Clojure中的集合是惰性的,
map
等都遵循惰性-在叠加
map
reduce
filter
等时,输入集合只经过一次,而不是多次。请参阅初始集合yes。但是他们创建了另一个集合,该集合被迭代(仅一次),然后传递给下一个函数,该函数迭代新集合(仅一次)。。。等等对于转换器,没有迭代的中间集合。我编辑了我的答案,希望对你有所帮助。@nha NB。问题实际上是关于减速器,而不是传感器。当然,你的答案无论如何都是正确的,因为他们在这一点上非常相似,但仍然是对于
(以3…
为例,
range
一次生成32个分块序列,
filter
生成一个分块序列,将输入中的每个分块缩小,以便只保留满足谓词的元素,然后
map
保留输入的分块大小,最后,
take
生成一个长度为3的非分块seq。请注意,
映射
过滤器
“阶段”不知道您计划将其输出传递到
(取3…
),因此,数字3根本就没有进入画面,因为它无法弄清它们的作用。@nha关于序列上“映射/映射/过滤”操作的草图,令人误解的是,看起来每个操作都会产生一个完全实现的中间序列,一个接一个。事实并非如此。相反,由于序列是惰性的,映射、映射和过滤以并行的方式进行(当然是在单个线程上),因为惰性序列正在被实现。谢谢-这听起来比我的另一个答案更正确。另一个答案意味着整个seq是在每个步骤之间实现的,这不可能是真的。这个分配成本是我理解的更有意义的东西,它是我们不想支付的成本,这就是减速机更快的原因。我想你可以这样阅读第一个答案……我认为这可能是无意的阅读,但如果它强烈地向你暗示,那么当然,这是误导。延迟序列处理一次发生一个元素或一个块(=最多32个元素的块),并且在需要时实现新的元素或块。我认为原始答案的正确观点是,当您堆叠lazy seq transformatio时
(require '[clojure.core.reducers :as r])
(reduce + (r/filter even? (r/map inc [1 1 1 2])))
;=> 6