Clojure-使用低内存处理大型文件
我正在处理60GB或更大的文本文件。这些文件分为可变长度的标题部分和数据部分。我有三个职能:Clojure-使用低内存处理大型文件,clojure,memory-efficient,file-processing,Clojure,Memory Efficient,File Processing,我正在处理60GB或更大的文本文件。这些文件分为可变长度的标题部分和数据部分。我有三个职能: head?区分头行和数据行的谓词 处理标题处理一个标题行字符串 处理数据处理一个数据行字符串 处理函数异步访问和修改内存中的数据库 我改进了从另一个SO线程读取文件的方法,它应该构建一个延迟的行序列。其思想是使用一个函数处理一些行,然后切换一次函数,并使用下一个函数继续处理 (defn lazy-file [file-name] (letfn [(helper [rdr]
区分头行和数据行的谓词head?
处理一个标题行字符串处理标题
处理一个数据行字符串处理数据
- 处理函数异步访问和修改内存中的数据库
(defn lazy-file
[file-name]
(letfn [(helper [rdr]
(lazy-seq
(if-let [line (.readLine rdr)]
(cons line (helper rdr))
(do (.close rdr) nil))))]
(try
(helper (clojure.java.io/reader file-name))
(catch Exception e
(println "Exception while trying to open file" file-name)))))
我用它来表示
(let [lfile (lazy-file "my-file.txt")]
(doseq [line lfile :while head?]
(process-header line))
(doseq [line (drop-while head? lfile)]
(process-data line)))
尽管这样做有效,但由于以下几个原因,它的效率相当低:
- 我必须过滤标题行并对其进行处理,然后重新解析整个文件并删除所有标题行以处理数据,而不是简单地调用
,直到到达数据,然后继续执行processhead
。这与processdata
的意图正好相反惰性文件
- 通过观察内存消耗情况,我发现这个程序虽然看起来很懒,但它可以使用尽可能多的RAM来将文件保存在内存中
head?
谓词的值使用multi方法来处理标题和数据,但我认为这会对速度产生严重影响,特别是在只有一种情况下,谓词结果总是从true变为false。我还没有做基准测试
使用另一种方法构建seq行并用迭代
解析它是否更好?我想这仍然会让我需要使用:while和:drop-while
在我的研究中,有几次提到使用NIO文件访问,这应该会提高内存使用率。我还不知道如何在clojure中以惯用的方式使用它
也许我还不太了解一般的想法,该如何处理该文件
一如既往,任何对TUTS的帮助、想法或指针都是非常值得赞赏的。
这里有几件事要考虑:
lein repl
的情况下,尝试验证您的声明“使用尽可能多的RAM将文件保存在内存中”
循环/recur
方法,而不是使用带有doseq
的两个循环。您希望解析的是第二个参数,如下所示(未测试):
这里还有另一个选项,就是将处理函数合并到文件读取函数中。因此,您可以立即处理它,而不只是cons
ing一个新行并返回它,通常您可以将处理函数作为参数而不是硬编码它
您当前的代码看起来处理是一种副作用。如果是这样的话,那么如果您合并了处理,您可能会消除懒惰。无论如何,您都需要处理整个文件(或者看起来是这样),并且是按行处理的。lazy seq
方法基本上只是将单行读取与单个处理调用对齐。在当前的解决方案中,由于您将读取(整个文件,逐行)与处理分开,因此需要惰性。如果您将一行的处理移到读数中,则不需要懒惰地这样做
您应该使用标准库函数 行seq,带有open和doseq将很容易完成这项工作 类似于:
(with-open [rdr (clojure.java.io/reader file-path)]
(doseq [line (line-seq rdr)]
(if (head? line)
(process-header line)
(process-data line))))
谢谢你的建议。我使用的
lazy file
方法是在我开始学习clojure时实现的,藏在io模块中并从那里开始使用。它的净效果确实与仅使用行序列
相同。另一方面,每行的if-else方法被证明比我所采用的方法慢得多(系数1.5)。重要的是,这里的运行时间以小时为单位;-)我理解你关于惰性文件的论点,但是打开和关闭文件会使这个函数更难进行单元测试。内存的问题是你在let
绑定中持有惰性seq的头。当您根据seq
文档处理这些行时,它们会保存在内存中。关于if
,如果由于文件大小而导致成本高昂,那么您打开文件两次的方法肯定是有效的。谢谢您的回答。昨天我写了一些测试用例来做基准测试。结果表明,A)消耗这么多内存的不是读取本身,而是数据库(顺便说一句,我的内存消耗声称源于运行编译后的应用程序)B)惰性文件
和行序列
的性能大致相同,考虑到速度和内存使用情况,令人惊讶的是,多方法和循环递归方法将需要大约150%的时间来打开文件两次并使用while/drop,而您在读取文件时喜欢使用递归方式。我将尝试的下一个想法是,我将让头解析器检查下一行是否是数据行(迭代器样式),如果是,则跳到数据解析器。If-else在每一行上都非常慢,但是文件被很好地定义为几百个标题行和数亿个数据行,读取标题只需要不到半秒钟的时间。我只是还不确定,如何结合蹦床和迭代器。。。
(with-open [rdr (clojure.java.io/reader file-path)]
(doseq [line (line-seq rdr)]
(if (head? line)
(process-header line)
(process-data line))))