我正在尝试使用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
条消息。这对我来说很有意义,因为它试图在所有这些IO
和Promise
对象的内存中建立一个庞大的列表。
几个问题:
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的正确使用,但我现在有几个可重复使用的,可组合的部分,我可以在我们系统的其他部分使用(这对我们来说是非常常见的模式)。
感谢您的帮助!
答案 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,演示了一个蹦床的迭代。