剂量q的clojure头保留,运行!循环

Twi*_*ice 5 clojure out-of-memory lazy-sequences

Clojure初学者/中级,

我有一个大的XML文件(~240M),我需要逐项处理ETL目的.有一些run-processing功能,它有许多副作用,数据库交互,写入日志等.

当我将所述函数应用于文件时,一切运行顺利:

...
(with-open [source (-> "in.xml"
                       io/file
                       io/input-stream)]
   (-> source
       xml/parse
       ((fn [x]
          ;; runs fine
          (run-processing conn config x)))))
Run Code Online (Sandbox Code Playgroud)

但是当我将相同的函数放入任何类型的循环(例如doseq)时,我得到OutOfMemoryException(GC Overhead).

...
(with-open [source (-> "in.xml"
                       io/file
                       io/input-stream)]
  (-> source
      xml/parse
      ((fn [x]
         ;; throws OOM GC overhead exception
         (doseq [i [0]]
            (run-processing conn config x))))))
Run Code Online (Sandbox Code Playgroud)

我不明白,头部保留在哪里导致GC开销异常?我已经尝试过run!,甚至loop recur没有doseq- 同样的事情发生了.

我的run-processing功能一定有问题吗?那么为什么我直接运行时表现良好?有点困惑,任何帮助是指定的.

Clo*_*tly 5

要理解为什么你的doseq不起作用,我们首先必须理解为什么(run-processing conn config x)起作用:

Clojure 的神奇之处在于本地清除:它分析任何代码,一旦最后一次使用本地绑定,就会在null运行该表达式之前将其设置为 (Java)。因此对于

(fn [x])
   (run-processing conn config x))
Run Code Online (Sandbox Code Playgroud)

运行前将x被清除run-processing。注意:禁用局部变量清除(编译器选项)时,您可能会遇到相同的 OOM 错误。

现在当你写下会发生什么:

(doseq [_ [0])
   (run-processing conn config x))
Run Code Online (Sandbox Code Playgroud)

编译器如何知道x最后一次使用的时间并清除它?我不可能知道它:它在循环中使用。所以它永远不会被清除,并且x会保留头部。

注意:当智能 JVM 实现了解到调用函数无法再访问本地内存位置并提供与垃圾收集器的绑定时,它可能会在将来改变这一点。不过,当前的实现并不那么聪明。

当然,修复它很容易:不要x在循环内使用。使用其他构造,例如run!这只是一个函数调用,并且会在调用之前正确清除本地run!。但是,如果将 seq 的头部传递给函数,该函数将保留头部,直到函数(闭包)超出范围。

  • 是的,同样的问题:如果您迭代 seq 两次,则第二次迭代将需要 seq 的头部。没有其他办法了。想一想:如果对序列进行垃圾回收,第二次迭代应该如何工作?您需要在一次迭代中完成这项工作,而不是两次。然后你可以在迭代时进行垃圾收集。 (2认同)