假设我有一个巨大的 lazy seq
,我想迭代它,以便我可以处理迭代过程中得到的数据。
我想要{strong>失去lazy seq
(已处理)的头部(GC'd),这样我就可以处理拥有数百万数据的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)))))
示例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
答案 0 :(得分:5)
我上述评论的副本
据我所知,以上所有都会失去头脑(前两个是明显的,因为你手动掉头,而doseq
的文件声称它不会保留头部)。
这意味着,如果传递给函数的lazy-seq-coll
未绑定到def
或let
的其他位置并在以后使用,则应该没有什么可担心的。所以(lose-head-fn (range))
不会占用你所有的记忆,而
(def r (range))
(lose-head-fn r)
可能会。
我能想到的唯一最佳实践不是def
可能无限(或者只是巨大的)序列,因为他们所有已实现的项目将永远存在于var中。
答案 1 :(得分:2)
一般情况下,你必须注意不要在本地或全局范围内保留一个懒惰seq的一部分,这个参数位于另一个涉及过度计算的laq seq之后。
例如:
(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)))
您将听到您的机箱/ cpu粉丝变得生动,如果您正在运行htop
或类似的,您会发现内存使用量增长相当快(大约1G)我前几秒钟。)
就像链表一样,clojure中lazy seq中的元素引用了接下来的seq部分。在上面的第二个示例中,first-ten
的第二个参数需要+
。因此,即使nth
很乐意不提及任何内容(毕竟,它只是在长列表中找到索引),first-ten
指的是序列的一部分,如上所述,必须保留对序列其余部分的引用。
相比之下,第一个示例计算(last first-ten)
,之后不再使用first-ten
。现在,对惰性序列的任何部分的唯一引用是nums
。当nth
完成其工作时,不再需要列表的每个部分,,因为没有其他任何内容引用此块中的列表,因为{{ 1}}遍历列表,已检查的序列占用的内存可以被垃圾收集。
考虑一下:
nth
为什么这与上面的第二个例子有类似的结果?因为序列将在其第一次实现时缓存(保存在内存中)(第一个;; Don't do this!
(let [nums (range)]
(time (nth nums 1e8))
(time (nth nums 1e8)))
),因为nums正在下一行使用。相反,如果我们对第二个(time (nth nums 1e8))
使用不同的序列,则不需要缓存第一个{em>序列,因此可以在处理时将其丢弃:< / p>
nth
因此,当您使用大型惰性seq时,请考虑是否仍有任何内容指向列表,如果有任何内容(全局变量是常见变量),那么将保留在内存中。