Scalaz 7 Iteratee处理大型zip文件(OutOfMemoryError)

时间:2013-04-26 03:14:15

标签: scala scalaz enumerator iterate scalaz7

我正在尝试使用scalaz iteratee包来处理恒定空间中的大型zip文件。我需要对zip文件中的每个文件执行一个长时间运行的进程。这些过程可以(并且应该)并行运行。

我创建了EnumeratorT,将每个ZipEntry膨胀为File个对象。签名如下:

def enumZipFile(f:File):EnumeratorT[IoExceptionOr[IO[File]], IO]

我想附加一个IterateeT,它将对每个文件执行长时间运行的过程。我基本上得到了类似的东西:

type IOE[A] = IoExceptionOr[A]

def action(f:File):IO[List[Promise[IOE[File]]]] = (
  consume[Promise[IOE[File]], IO, List] %=
  map[IOE[File], Promise[IOE[File]], IO](longRunningProcess) %=
  map[IOE[IO[File]], IOE[File], IO](_.unsafePerformIO) &=
  enumZipFile(f)
).run

def longRunningProcess:(iof:IOE[File]):Promise[IOE[File]] =
  Promise { Thread.sleep(5000); iof }

当我尝试运行它时:

action(new File("/really/big/file.zip")).unsafePerformIO.sequence.get

我收到java.lang.OutOfMemoryError: Java heap space条消息。这对我来说很有意义,因为它试图在所有这些IOPromise对象的内存中建立一个庞大的列表。

几个问题:

  • 有没有人对如何避免这个有任何想法?感觉我正在接近这个问题,因为我真的只关心longRunningProcess的副作用。
  • 这里的Enumerator方法是错误的方法吗?

我几乎没有想法,所以一切都会有所帮助。

谢谢!

更新#1

这是堆栈跟踪:

[error] java.lang.OutOfMemoryError: Java heap space
[error]         at scalaz.Free.flatMap(Free.scala:46)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:61)
[error]         at scalaz.effect.IOFunctions$$anon$5.apply(IO.scala:222)
[error]         at scalaz.effect.IO$$anonfun$flatMap$1.apply(IO.scala:62)

我目前正在接受nadavwr的建议,以确保一切都像我认为的那样。我会报告任何更新。

更新#2

使用以下答案中的想法,我找到了一个不错的解决方案。正如huynhjl所建议的那样(我使用nadavwr建议分析堆转储),consume导致每个膨胀的ZipEntry被保存在内存中,这就是进程内存不足的原因。我将consume更改为foldM并更新了长时间运行的流程,只返回Promise[IOE[Unit]]而不是对文件的引用。这样,我最后收集了所有IoExceptions。这是工作解决方案:

def action(f:File):IO[List[Promise[IOE[Unit]]]] = (
  foldM[Promise[IOE[Unit]], IO, List[Promise[IOE[Unit]]]](List.empty)((acc,x) => IO(x :: acc)) %=
  map[IOE[File], Promise[IOE[Unit]], IO](longRunningProcess) %=
  map[IOE[IO[File]], IOE[File], IO](_.unsafePerformIO) &=
  enumZipFile(f)
).run

def longRunningProcess:(iof:IOE[File]):Promise[IOE[Unit]] =
  Promise { Thread.sleep(5000); iof.map(println) }

此解决方案会在异步上传每个条目时对其进行膨胀。最后,我有一个包含任何错误的已完成Promise个对象的大量列表。我仍然不完全相信这是对Iteratee的正确使用,但我现在有几个可重复使用的,可组合的部分,我可以在我们系统的其他部分使用(这对我们来说是非常常见的模式)。

感谢您的帮助!

3 个答案:

答案 0 :(得分:4)

请勿使用consume。请参阅我最近的其他答案:How to use IO with Scalaz7 Iteratees without overflowing the stack?

foldM可能是更好的选择。

还尝试将文件映射到其他东西(如成功返回代码),看看是否允许JVM垃圾收集膨胀的zip条目。

答案 1 :(得分:1)

有多贵(就你的内存而言longRunningProcess?文件通缩怎么样?它们被执行的次数是你想要的吗?(一个简单的计数器会有帮助)

堆栈跟踪将有助于确定打破骆驼背部的稻草 - 有时这是罪魁祸首。

如果您想确定占用如此多内存的内容,可以使用-XX:+HeapDumpOnOutOfMemoryError JVM参数,然后使用VisualVM,Eclipse MAT或其他堆分析器对其进行分析。

跟进

对你来说,你在列举承诺似乎很奇怪。启动独立于枚举器和迭代器的计算是违反直觉的。基于迭代的解决方案可能更好地由枚举器提供,该枚举器返回'inert'元素而不是promises。不幸的是,这将使您对单个文件的处理成为串行,但这是ya的迭代 - 非阻塞流处理。

基于演员的解决方案更适合恕我直言,但是演员和迭代者(特别是后者)似乎对你想要完成的事情(至少你正在分享的部分)有些过分。

请考虑来自Scala 2.10的scala.concurrent包的简单期货/承诺,并确保同时查看Scala的并行集合。在这些证明不足之前,我不会在代码中引入其他概念。尝试定义一个固定大小的ExecutionContext来约束你的并行性。

答案 2 :(得分:0)

我在快速阅读后开始回答,并且不知何故“堆栈溢出”卡在我脑海中,而不是“内存不足错误”......必须是URL :-)

但是,依赖于递归的功能计算容易受到堆栈溢出的影响,因此我已经为任何身体绊倒留下了答案,并承诺尝试提出更相关的答案。

如果你得到的是堆栈溢出,你需要一个'trampoline',一个在递归之间增加堆栈计算的构造。

请参阅Learning Scalaz Day 18中标题为“Stackless Scala with Free Monads”的部分,这是@ eed3si9n优秀系列文章的一部分。

参见@mpilquist的this gist,演示了一个蹦床的迭代。