在Clojure中处理大文件时出现OutOfMemory错误

时间:2012-04-11 00:39:33

标签: clojure out-of-memory

我正在关注algo-class.org课程,其中一个编程作业提供了一个格式如下的文件:

1 2
1 5
2 535

...

有超过500万个这样的行,我想在文件中读取并将其转换为整数向量的向量,如下所示:[[1 2] [1 5] [2 535] ...]。

(defn to-int-vector [s]
    (vec (map #(Integer/parseInt %) (re-seq #"\w+" s))))    

(def ints (with-open [rdr (clojure.java.io/reader "<file>")]
               (doall (map to-int-vector (line-seq rdr)))))

所以我相信这样,我不是将整个文件保存在内存中,而只是生成一个大的整数向量。但是我得到了OutOfMemoryError。我尝试通过运行rand-int来生成相同大小和相同格式的向量,并且工作正常。

看起来内存问题是由生成的临时对象引起的?在这样的情况下,clojure的理想方式是什么?

更新

是的,我意识到我持有整个整数向量。我已经提高了堆大小,现在可以了。我很感兴趣的是,一个载体和500万个元素(1000万个整数)可以占用这么多内存 - 我必须为jvm分配3g。有没有其他方法可以记住内存?

3 个答案:

答案 0 :(得分:5)

你不会相信实现的懒惰seq会增加多少开销。我在64位操作系统上测试了它:它类似于120字节。这对于每个懒惰的seq成员来说都是纯粹的开销。另一方面,向量具有相当低的开销,并且在给定足够大的向量的情况下基本上与Java数组相同。因此,请尝试将doall替换为vec

让我们看看你在没有开销的情况下花了多少内存。你有5e6对整数 - 这是5e6 x 8 = 40 MB。你可以通过使用short来节省并节省50%(我再说一遍 - 这不算是父集合的开销,并且持有该对的每个向量实例都有自己的开销)。

保存的下一步是对外部集合和对使用原始数组。它仍然是一个非常实用的解决方案,因为阵列是可选的并且与语言很好地集成。为此,您只需将vec的两个匹配项替换为to-array

<强>更新

IntegerShort之间的差异并不大,因为两者仍然是完全成熟的对象。使用short-array(或int-array)代替to-array,将数字对存储为原始数组可以节省更多。

答案 1 :(得分:2)

很难利用懒惰并同时封装with-open。懒惰在你的情况下很重要,因为它可以在内存中只包含行的“相关”部分或int-vector序列。

该问题的一个解决方案是不封装with-open并将整个行处理逻辑包含在with-open形式的动态范围内:

(with-open [rdr (clojure.java.io/reader "<file>")]
  (doseq [int-vector (map to-int-vector (line-seq rdr))]
    (process int-vector)))

这里的两个重要细节是你不存储行和int-vector序列,并且你只在with-open形式中使用它们。这些细节确保已经处理的序列部分可以被垃圾收集,并且文件流在整个处理过程中保持打开状态。

答案 2 :(得分:1)

def中的(def ints将确保整个结果存储在内存中 这至少与文件一样大,因为每行的数字都是 存储在一个集合中,在这种情况下是一个vec,它也占用了空间。

默认情况下java也会拒绝使用计算机中的所有内存,您可能需要设置maxHeapSize参数。

如果你开始使用新的repl(尚未持有任何大型列表),你的内存是否仍然不足?

相关问题