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

欢迎!请参阅 关于 页面以获取有关此功能的更多信息。

0
Clojure

我曾读到Clojure序列在其值实现时会“缓存”它们的值(例如在这这里)。这通常在讨论使用transducer转换值而不是链式序列函数的优势时出现,后者方法会创建“中间缓存的序列”。

然而,我也遇到过这样的建议“处理序列时不要一条道走到黑”,因为保持对序列开头的引用会阻止GC释放用于存储序列中之前看到的值的内存。

在我目前的理解水平上,这些观点似乎相互矛盾。如果序列缓存其结果,那么为什么保留头部很重要?缓存不是意味着所有已实现元素都会保留吗?或者从另一个角度来看,如果GC可以随意清理它们,为什么序列还要缓存已实现的值呢?

我知道我肯定遗漏了一些明显的东西,但我会非常感激一个解释。

1 答案

+2
头像 By
selected By
 
最佳答案

一个已实现序列是内存中对象的链,链中的每个链接都指向一个值和下一个链接,直到最终指向一个函数对象,该对象是序列剩余未实现的部分。对序列中任意链接的引用都会导致从该点开始的链的其余部分被强引用并无法被GC。

如果您只指向链的未实现“末端”,则您身后的所有链接都可以被回收。当您“持有头部”时,该指针指向链的起点,这会阻止从开始到未实现点的N个链接被保留在内存中。

例如

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

vs

(count (repeat 100000000 "abcdef"))

这可以遍历序列,允许回收计数器后面的链接。(注意,这里的range是人们在这些示例中常用的典型东西,但它在常见情况下有一个优化的count。)

头像 By
谢谢Alex,这都很有道理。我还不太理解的是,为什么说序列是“缓存”的?如果您(如您的第二个示例中所示)没有对先前已实现值的引用,那么在什么情况下会重用先前已实现值(即缓存命中)?或者说,“缓存”这个词在这里并不意味着值的重用,只是它们在内存中一直存在,直到进行GC遍历吗?
头像 By
我相信答案是,这里的缓存意味着如果确实持有头部,即在第一次遍历序列时计算每个元素,然后您在第二次、第三次等遍历时再次遍历序列并同时持有头部,则不会在后续遍历时重新计算。这些值是通过消耗内存来避免重新计算而缓存的。
这里“缓存”的意思是指链中的链接保持在该点的计算值。
谢谢你们。那么,链式序列函数潜在的性能问题是不是意味着中间序列会导致更频繁的垃圾回收来完成已实现值的清除(在通常情况下,前面的值没有被显式引用)?
是的,嵌套序列函数将创建N层节点(与转置的1层节点相比),这意味着是N倍的垃圾,因此收集次数也是N倍。
...