ale*_*159 5 memory csv garbage-collection clojure
我正在尝试解析一个 50MB 的 CSV 文件。~2500 行,~5500 列,一列是字符串(日期为 yyyy-mm-dd),其余是带有很多空点的浮点数。我需要能够访问所有数据,所以想要实现完整的文件,这在那个大小下应该是可能的。
我尝试了以下几个选项:
(with-open [rdr (io/reader path)] (doall (csv/read-csv rdr))))
手动使用line-seq字符串并将其解析为数字的稍微手动的方法。
我在单个 JVM 上的使用量slurp增加了 100MB,是文件大小的 2 倍。在解析数据时,我会根据它的完成方式增加 1-2GB。如果我多次打开文件并将其解析为同一个变量,内存使用量会不断增加,最终会出现内存错误并且程序失败。(我知道查看任务管理器并不是查看内存泄漏的最佳方法,但事实是程序失败了所以某处存在泄漏)
打开文件的正确方法是什么?我的最后一个用例是我每天都会获取一个新文件,我希望服务器应用程序每天打开文件并处理数据,而不会耗尽内存并需要重新启动服务器。
编辑:为了比较,使用 Python pandas 读取该文件将消耗大约 100MB 的内存,并且随后重新读取该文件不会继续增加内存使用量。
Edit2:这是一个使用本地原子尝试查看发生了什么的最小示例:
(defn parse-number [s] (if (= s "") nil (read-string s)))
(defn parse-line [line]
(let [result (atom [])]
(doseq [x (clojure.string/split line #",")]
(swap! result conj (parse-number x)))
@result))
(defn line-by-line-parser [file]
(let [result (atom [])]
(with-open [rdr (clojure.java.io/reader file)]
(doseq [line (line-seq rdr)]
(swap! result conj (parse-line line)))
@result)))
;in the repl:
(def x (line-by-line-parser "C:\\temp\\history.csv")) ; memory goes up 1GB
(def x (line-by-line-parser "C:\\temp\\history.csv")) ; memory goes up an extra 1GB
; etc
Run Code Online (Sandbox Code Playgroud)
非常感谢!
正如我在评论中提到的,看到你如何使用原子,有两件事让我困扰:
swap调用都会生成一堆垃圾对象,这些垃圾对象可以解释内存使用情况。第一和第二可以使用into或来解决reduce。这样做的好处是,生成的代码会更短。
我更熟悉reduce,所以这里是一个使用它的例子:
(defn parse-number [s] (if (= s "") nil (read-string s)))
(defn parse-line [line]
(reduce #(conj %1 (parse-number %2))
[]
(clojure.string/split line #",")))
(defn line-by-line-parser [file]
(with-open [rdr (clojure.java.io/reader file)]
(reduce #(conj %1 (parse-line %2))
[]
(line-seq rdr))))
Run Code Online (Sandbox Code Playgroud)
由于我没有你的测试数据,我只能猜测它可以解决你的问题。因此,如果您对其进行测试并报告是否有任何改进,我将很高兴。