如何在 Clojure 中忘记惰性序列的 head(GC'd)?

How to forget head(GC'd) for lazy-sequences in Clojure?

假设我有一个 巨大的 lazy seq 并且我想对其进行迭代,以便我可以处理在迭代过程中获得的数据。

问题是我想 失去 lazy seq(已处理)的头(GC'd),这样我就可以处理具有 数百万数据 而没有 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)))))

示例 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))))))

示例 3

(doseq [i lazy-seq-coll]
  ;;do some processing...
  )

更新: 这个答案里也有解释 here

复制我上面的评论

据我所知,以上所有都会失去头部(前两个很明显,因为你手动放下头部,而 doseq 的文档声称它不会保留头部)。

这意味着如果您传递给函数的 lazy-seq-coll 未与 deflet 绑定到其他地方并在以后使用,则无需担心。所以 (lose-head-fn (range)) 不会吃掉你所有的内存,而

(def r (range)) 

(lose-head-fn r) 

可能会。

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

一般来说,您必须注意不要在本地或全局保留对惰性序列的一部分的引用,该部分先于另一个涉及过多计算的惰性序列

例如:

(let [nums (range)
      first-ten (take 10 nums)]
  (+ (last first-ten) (nth nums 100000000)))
=> 100000009

这在现代机器上大约需要 2 秒。这个怎么样?不同之处在于最后一行,其中交换了 + 的参数顺序:

;; Don't do this!
(let [nums (range)
      first-ten (take 10 nums)]
  (+ (nth nums 100000000) (last first-ten)))

你会听到你的 chassis/cpu 粉丝活跃起来,如果你是 运行 htop 或类似的人,你会看到内存使用量增长得相当快(大约 1G在最初的几秒钟对我来说)。

怎么回事?

与链表非常相似,clojure 中惰性序列中的元素引用序列中接下来的部分。在上面的第二个例子中,+ 的第二个参数需要 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)))

为什么这与上面的第二个例子有相似的结果?因为序列将在第一次实现时(第一个 (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"

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