请在2024年Clojure调查!分享您的想法。

欢迎!请查看关于页面,了解这个工具的一些更多信息。

0
Clojure

我读到了Clojure中的序列会在它们被实现时キャッシュする(例えば、ここ此处此处)。当讨论使用transducer来转换值而不是链式调用序列函数的相对优点时,这个问题似乎经常出现,后一种方法会创建“中间缓存的序列”。

然而,我也遇到过关于处理序列的建议“不要纠结”,因为保留序列开头的引用会阻止GC释放序列中之前已看到的值所占用的内存。

在这些想法中,我在当前的理解水平上似乎存在冲突。如果序列缓存结果,那么保留头部有什么意义?缓存不是意味着保留了所有已实现的元素吗?或者换一个角度来理解,如果GC可以清理这些值,那么为什么序列要缓存它们的实现值呢?

我知道肯定有什么我忽略的地方,但我会非常欣赏一个解释。

1 回答

+2

被选中
 
最佳答案

一个已实现的序列是内存中对象的链,链条中的每个链接都指向一个值和下一个链接,直到你最终到达一个函数对象,这是序列中尚未实现的剩余部分。对实现链中任何链接的引用都会导致从该点开始到链的其余部分被强引用,无法被垃圾回收(gc'ed)。

如果你只是指向该链的未实现“末尾”,那么你“后面”的所有链接都可以被收集。当“持有头端”时,这个指针指向链的开始,这阻止了从开始到未实现点的N个链接被保存在内存中。

例如

(def r (repeat 100000000 "abcdef"))   ;; r holds a strong reference to the head
(count r)

vs

(count (repeat 100000000 "abcdef"))

可以遍历序列seq,允许gc'ed从计数器后面的链接开始。(注意,《range》通常用于这些示例,但它具有针对典型情况的优化《count》。)

亚历克斯,这些都很有道理。我还是不太明白为什么序列会被说成是“缓存的”?如果在第二个例子中,没有引用之前实现的值,那么在什么场景下会再次使用之前实现的值(即缓存命中)?或者这里的“缓存”这个词的含义并不是指值的复用,而是它们仍然存储在内存中,直到垃圾回收周期发生?
我认为答案是,这里的缓存意味着如果你确实持有了头端,那么在第一次遍历序列时,每个元素都必须被计算,如果你后来再次遍历序列同时持有头端,就不会再进行重新计算。这些值通过消耗内存来避免重复计算。
这里的“Cache”只是意味着链条中的链接保持了在该点计算的值。
谢谢你们两位。那么,可以这么说,链式序列函数的潜在性能问题是,中间序列可能引起更频繁的GC滑动以清除已实现值(在通常情况下,之前值没有被显式引用)吗?
是的,嵌套序列函数将创建N层节点(相对于1层变换器减少),这意味着垃圾成N倍,因此收集也成N倍。
...