【问题标题】:What will the behaviour of line-seq be?line-seq 的行为会是什么?
【发布时间】:2014-01-24 12:02:37
【问题描述】:

如果我使用doseq 进行迭代但保留第一个元素的一部分,我想了解惰性序列的行为。

 (with-open [log-file-reader (clojure.java.io/reader (clojure.java.io/file input-file-path))]

    ; Parse line parse-line returns some kind of representation of the line.
    (let [parsed-lines (map parse-line (line-seq log-file-reader))
          first-item (first parsed-lines)]

          ; Iterate over the parsed lines
          (doseq [line parsed-lines]
            ; Do something with a side-effect  
          )))

我不想保留任何列表,我只想对每个元素执行副作用。我相信没有first-item不会有问题。

我的程序中存在内存问题,我认为可能在parsed-line 序列的开头保留对某些内容的引用意味着整个序列都已存储。

这里定义的行为是什么?如果正在存储序列,是否有一种通用的方法来获取对象的副本并使序列的已实现部分能够被垃圾收集?

【问题讨论】:

    标签: clojure lazy-sequences


    【解决方案1】:

    序列保持发生在这里

    ...
    (let [parsed-lines (map parse-line (line-seq log-file-reader))
    ...
    

    文件中的行序列被延迟生成和解析,但整个序列被保留在let的范围内。这个序列是在doseq中实现的,但是doseq不是问题,它不做sequence-holding。

    ...
    (doseq [line parsed-lines]
     ; Do something
    ...
    

    您不一定关心let 中的序列保持,因为let 的范围是有限的,但这里大概您的文件很大和/或您停留在let 的动态范围内while,或者可能在“做某事”部分中返回一个包含它的闭包。

    请注意,保持序列的任何给定元素,包括第一个元素,都不会保持序列。如果您将 head 视为 Prolog 中“列表的头部”中的第一个元素,那么术语 head-holding 有点用词不当。问题在于对序列的引用。

    【讨论】:

    • 仅供参考,我正在处理数千兆字节的文件。您是说 let 保留序列,即使它是懒惰地生产和消费的?那么我应该使用什么语法来执行上述操作?我刚刚在 line-seq 的尾部使用循环 + recur 重写了它,并且 RAM 使用量明显更小。但它看起来几乎没有那么好。有没有办法在没有 seq-holding 的情况下在范围内获得延迟评估的地图?
    • 如果您不想回答,我意识到绑定是针对 cons-cell-type-element 而不是“整个惰性序列”。我应该这样做的方法是将parsed-lines 放入doseq 绑定中。
    • 编译器应在最后一次引用parsed-lines 后立即在此处释放它,即当doseq 启动时。此处发布的代码不需要大量内存,除非注释掉的行也引用了大的惰性序列。
    • @amalloy 我从未意识到编译器会这样做。我可以在大多数情况下重现不断增长的内存消耗,但副作用不是引用序列,但它不在 Java 堆中。可疑操作系统(此处为 Win 7 64 位 Cygwin)。
    • @Joe 见上文。您是用完了 Java 堆还是只是在消耗系统内存?如果是堆,您是否引用了序列? Clojure 版本(我相信 amalloy 的评论适用于 v1.2+,你肯定有)?如果是系统,操作系统?
    【解决方案2】:

    一旦 JVM 成为 Java 堆的一部分,它就永远不会将内存返回给操作系统,除非您对其进行不同的配置,否则默认的最大堆大小非常大(通常是可用 RAM 的 1/4)。因此,如果您只是遇到诸如“天哪,这会占用大量内存”之类的模糊问题,而不是“嗯,JVM 抛出了 OutOfMemoryError”,那么您可能只是没有按照您想要的方式调整 JVM行为。 partition-by 有点急切,因为它一次在内存中保存一个或两个分区,但除非你的分区很大,否则你不应该用这个代码耗尽堆空间。尝试设置-Xmx100m,或者任何你认为对你的程序来说合理的堆大小,看看你是否有问题。

    【讨论】:

    • 感谢您的帮助。是的,分区非常大(~OTOO 1 GB)。我已经对代码进行了几次迭代,我必须回去确认特征并在星期一更新。此操作完成后 JVM 返回大量 RAM,这就是我怀疑的原因。我的堆设置得足够大(4GB),但我只是看到使用量随着数据大小的增加而增加,并推断出大型现实世界数据的消耗,我认为有些问题。
    • re partition-by 我听说它返回了惰性序列的惰性序列。您是说分区本身已实现并存储。如果分区是完整存储的,那将回答我的问题(它们非常大)。
    • 确实是急切序列的惰性序列。
    猜你喜欢
    • 2013-02-17
    • 2013-03-15
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 2022-01-23
    相关资源
    最近更新 更多