5

我正在学习 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。有没有其他方法可以降低内存?

4

3 回答 3

5

你不会相信实现的惰性序列会带来多少开销。我在 64 位操作系统上对此进行了测试:大约是 120 字节。这对于每个惰性 seq 成员来说都是纯粹的开销。另一方面,向量具有相当低的开销,并且在给定足够大的向量的情况下与 Java 数组基本相同。所以尝试替换doallvec.

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

保存的下一步是为外部集合和对使用原始数组。它仍然是一个非常实用的解决方案,因为数组是可排序的并且与语言很好地集成。为此,您只需将出现的两次替换vecto-array

更新

Integer和之间的差异Short并不大,因为两者仍然是成熟的对象。将数字对存储为原始数组,使用short-array(or int-array) 而不是to-array.

于 2012-04-11T07:51:17.127 回答
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)))

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

于 2012-04-11T17:48:43.687 回答
1

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

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

如果您从一个新的 repl 开始(还没有保存任何大列表),您是否仍然内存不足?

于 2012-04-11T02:48:24.710 回答