2016-03-15 91 views

回答

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游戏中,没有每步分配开销,因此速度更快。当你需要时,懒惰是伟大的,但是当你不需要时,你不应该为此付出代价。

实现懒惰序列的带有一个(线性)分配成本:每从懒惰SEQ另一元件被实现,带seq的其余被存储在新的thunk时间,以及表示这样的'thunk'是a new clojure.lang.LazySeq object

我相信这些LazySeq对象是引用中引用的分配开销。对于减速器,没有逐步实现惰性seq元素,因此根本没有实例化LazySeq thunk。

+0

谢谢 - 这听起来比我的其他答案更正确。另一个答案意味着整个seq是在每一步之间实现的,这是不对的。这种分配成本是我理解的东西更有意义,而这是我们不想支付的成本,这是减速器更快的成本。 – zlatanski

+1

我想你可以通过这种方式阅读第一个答案......我认为这可能是一个意外的阅读,但如果它强烈地向你表明,那么肯定的是,这是误导。惰性seq处理一次发生一个元素或一个块(最多32个元素的块),并在需要时实现新元素或块。我认为原始答案试图做出的正确观点是,当你将懒惰的seq转换进行堆栈时,每一层都需要分配它自己的懒惰seq对象,每个元素或块有一个“单元”(尽管它们当然会被懒惰地实现)。 –

+0

@MichałMarczyk:第一个答案说:“当你结合像map这样的操作,过滤那些函数遍历一个集合,并返回一个新的集合,然后传递给下一个函数**”,这听起来毫不含糊。 nha还一直在评论中捍卫这一点,所以我放弃了争论。后来看来,他/她修补了更广泛的答案。 – zlatanski

5

相关的地方“注意,有没有产生中间集合。”

这是改进,因为垃圾收集器的压力比较小。

当你结合像map,filter这样的操作时,那些函数迭代一个集合并返回一个新的集合,然后传递给下一个函数。减速器情况并非如此。

因此,它们可以用作那些功能(也就是说,它们以相同的方式组成)并且可以应用于相同的集合。但是增加了性能。

具有和不减速的地图/地图/过滤操作的粗糙和完全不科学草图如下:

没有减速器

初始集合=>地图=>中介集合=>地图=> intermediary collection => filter => final collection

没有reducer(即clojure.core map/filter函数)int中间集合是懒惰生产的。 也就是说,只有在下一个处理步骤需要时才产生新的元素,一次一个元素或一个块。

注意:块是由32个元素组成的块(尽管这应该被视为实现细节)。这是出于效率的原因,以避免从一次计算跳到另一次计算。

查看map函数return value例如:它返回lazy-seqtransducers的情况除外)。

随着减速

初步收集=>地图/地图/过滤器减速=>最终收集

如果您需要处理整个集合快,那么懒惰也是在越来越表现方式。删除懒惰seqs的中间层提供了性能增益。 Reducers可以做到这一点并且可以快速处理收集,因此速度更快。您还可以保持相同的功能来缓解切换。

请仔细阅读下面的意见,尤其是来自@米哈尔Marczyk

+0

我不相信这是真的。 Clojure中的集合是懒惰的,'map' et.al坚持懒惰 - 当叠加'map','reduce','filter'等时,只有一次通过输入集合,而不是多次。参见http:///clojure.org/reference/sequences – zlatanski

+2

在最初的收集是。但是,他们创建了另一个集合,迭代(仅一次),然后传递给下一个函数,该函数遍历新集合(仅再次)...等等。换能器没有迭代的中间集合。我编辑了我的答案,希望它有帮助。 – nha

+0

如果你说的是真的,那么'(以3(地图增量(甚至过滤?(范围999999999))))'将花费很长时间并使用大量内存。但它需要30个用户,并且几乎不消耗内存 – zlatanski