如何忘记Clojure中懒惰序列的头部(GC'd)?

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)

更新:这里的答案也有解释

lee*_*ski 5

我的上述评论的副本

据我所知,以上所有都会失去头脑(前两个是明显的,因为你手动掉头,而doseq文件声称它不会保留头部).

这意味着如果lazy-seq-coll传递给函数的函数没有绑定到其他地方deflet稍后使用,那么应该没有什么可担心的.所以(lose-head-fn (range))不会吃掉你所有的记忆

(def r (range)) 

(lose-head-fn r) 
Run Code Online (Sandbox Code Playgroud)

可能会.

我能想到的唯一最佳实践不是def可能无限(或只是巨大的)序列,因为它们所有已实现的项目将永远存在于var中.


Jos*_*osh 5

一般来说,您必须小心,不要在本地或全局保留惰性 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)

因此,当您使用大型惰性序列时,请考虑是否有任何东西仍然指向列表,如果有任何东西(全局变量是常见的),那么它将保存在内存中。