Ert*_*tin 5 garbage-collection clojure lazy-sequences
假设我有一个巨大的 lazy seq,我想迭代它,所以我可以处理迭代过程中得到的数据.
问题是我想失去头(GC'd)的lazy seq(即处理),这样我就可以有seqs工作的数以百万计的数据,而不必OutofMemoryException.
我有3个例子,我不确定.
你能为此目的提供最佳实践(例子)吗?
这些功能失去了作用吗?
例1
(defn lose-head-fn
[lazy-seq-coll]
(when (seq (take 1 lazy-seq-coll))
(do
;;do some processing...(take 10000 lazy-seq-coll)
(recur (drop 10000 lazy-seq-coll)))))
Run Code Online (Sandbox Code Playgroud)
例2
(defn lose-head-fn
[lazy-seq-coll]
(loop [i lazy-seq-coll]
(when (seq (take 1 i))
(do
;;do some processing...(take 10000 i)
(recur (drop 10000 i))))))
Run Code Online (Sandbox Code Playgroud)
例3
(doseq [i lazy-seq-coll]
;;do some processing...
)
Run Code Online (Sandbox Code Playgroud)
更新:这里的答案也有解释
我的上述评论的副本
据我所知,以上所有都会失去头脑(前两个是明显的,因为你手动掉头,而doseq文件声称它不会保留头部).
这意味着如果lazy-seq-coll传递给函数的函数没有绑定到其他地方def或let稍后使用,那么应该没有什么可担心的.所以(lose-head-fn (range))不会吃掉你所有的记忆
(def r (range))
(lose-head-fn r)
Run Code Online (Sandbox Code Playgroud)
可能会.
我能想到的唯一最佳实践不是def可能无限(或只是巨大的)序列,因为它们所有已实现的项目将永远存在于var中.
一般来说,您必须小心,不要在本地或全局保留惰性 seq 中位于另一个涉及过多计算的惰性 seq 之前的部分的引用。
例如:
(let [nums (range)
first-ten (take 10 nums)]
(+ (last first-ten) (nth nums 100000000)))
=> 100000009
Run Code Online (Sandbox Code Playgroud)
在现代机器上这大约需要 2 秒。不过这个怎么样?不同之处在于最后一行,其中参数的顺序被+交换:
;; Don't do this!
(let [nums (range)
first-ten (take 10 nums)]
(+ (nth nums 100000000) (last first-ten)))
Run Code Online (Sandbox Code Playgroud)
您会听到机箱/CPU 风扇运转的声音,如果您正在运行htop或类似的情况,您会看到内存使用量增长得相当快(对我来说,前几秒大约为 1G)。
与链表非常相似,clojure 中的惰性 seq 中的元素引用接下来的 seq 部分。在上面的第二个示例中,first-ten需要第二个参数+。因此,尽管nth很高兴不保留对任何内容的引用(毕竟,它只是在长列表中查找索引),但first-ten引用序列的一部分,如上所述,必须保留对序列其余部分的引用。
相比之下,第一个示例计算(last first-ten),此后first-ten不再使用。现在对惰性序列任何部分的唯一引用是nums。正如nth它的工作一样,它完成的列表的每个部分都不再需要,并且由于没有其他内容引用此块中的列表,因此nth在遍历列表时,已检查的序列占用的内存可以被垃圾收集。
考虑一下:
;; Don't do this!
(let [nums (range)]
(time (nth nums 1e8))
(time (nth nums 1e8)))
Run Code Online (Sandbox Code Playgroud)
为什么这与上面第二个例子有相似的结果?因为该序列将在第一次实现(第一个)时被缓存(保存在内存中)(time (nth nums 1e8)),因为 nums 正在下一行中使用。相反,如果我们对第二个使用不同的nth序列,则无需缓存第一个,因此可以在处理时将其丢弃:
(let [nums (range)]
(time (nth nums 1e8))
(time (nth (range) 1e8)))
"Elapsed time: 2127.814253 msecs"
"Elapsed time: 2042.608043 msecs"
Run Code Online (Sandbox Code Playgroud)
因此,当您使用大型惰性序列时,请考虑是否有任何东西仍然指向列表,如果有任何东西(全局变量是常见的),那么它将被保存在内存中。