背景
如该问题所述,我正在使用Scalaz 7迭代器在恒定堆空间中处理大量(即无边界)数据流。
我的代码如下所示:
type ErrorOrT[M[+_], A] = EitherT[M, Throwable, A]
type ErrorOr[A] = ErrorOrT[IO, A]
def processChunk(c: Chunk, idx: Long): Result
def process(data: EnumeratorT[Chunk, ErrorOr]): IterateeT[Vector[(Chunk, Long)], ErrorOr, Vector[Result]] =
Iteratee.fold[Vector[(Chunk, Long)], ErrorOr, Vector[Result]](Nil) { (rs, vs) =>
rs ++ vs map {
case (c, i) => processChunk(c, i)
}
} &= (data.zipWithIndex mapE Iteratee.group(P))
问题
我似乎遇到了内存泄漏,但是我对Scalaz / FP不够熟悉,无法知道该错误是在Scalaz中还是在我的代码中。直观地,我希望此代码仅需要(大约)P倍Chunk
-size空间。
注意:我在遇到类似的问题OutOfMemoryError
时遇到了,但我的代码未使用consume
。
测试中
我进行了一些测试以尝试找出问题所在。总而言之,仅当同时使用zipWithIndex
和时才出现泄漏group
。
// no zipping/grouping
scala> (i1 &= enumArrs(1 << 25, 128)).run.unsafePerformIO
res47: Long = 4294967296
// grouping only
scala> (i2 &= (enumArrs(1 << 25, 128) mapE Iteratee.group(4))).run.unsafePerformIO
res49: Long = 4294967296
// zipping and grouping
scala> (i3 &= (enumArrs(1 << 25, 128).zipWithIndex mapE Iteratee.group(4))).run.unsafePerformIO
java.lang.OutOfMemoryError: Java heap space
// zipping only
scala> (i4 &= (enumArrs(1 << 25, 128).zipWithIndex)).run.unsafePerformIO
res51: Long = 4294967296
// no zipping/grouping, larger arrays
scala> (i1 &= enumArrs(1 << 27, 128)).run.unsafePerformIO
res53: Long = 17179869184
// zipping only, larger arrays
scala> (i4 &= (enumArrs(1 << 27, 128).zipWithIndex)).run.unsafePerformIO
res54: Long = 17179869184
测试代码:
import scalaz.iteratee._, scalaz.effect.IO, scalaz.std.vector._
// define an enumerator that produces a stream of new, zero-filled arrays
def enumArrs(sz: Int, n: Int) =
Iteratee.enumIterator[Array[Int], IO](
Iterator.continually(Array.fill(sz)(0)).take(n))
// define an iteratee that consumes a stream of arrays
// and computes its length
val i1 = Iteratee.fold[Array[Int], IO, Long](0) {
(c, a) => c + a.length
}
// define an iteratee that consumes a grouped stream of arrays
// and computes its length
val i2 = Iteratee.fold[Vector[Array[Int]], IO, Long](0) {
(c, as) => c + as.map(_.length).sum
}
// define an iteratee that consumes a grouped/zipped stream of arrays
// and computes its length
val i3 = Iteratee.fold[Vector[(Array[Int], Long)], IO, Long](0) {
(c, vs) => c + vs.map(_._1.length).sum
}
// define an iteratee that consumes a zipped stream of arrays
// and computes its length
val i4 = Iteratee.fold[(Array[Int], Long), IO, Long](0) {
(c, v) => c + v._1.length
}
问题
- 是我代码中的错误吗?
- 如何在恒定的堆空间中完成这项工作?
6
我最终在Scalaz中将此报告为问题。
—
亚伦·诺夫斯特鲁普
这不会很有趣,但是您可以尝试
—
huynhjl
-XX:+HeapDumpOnOutOfMemoryError
使用eclipse MAT eclipse.org/mat分析转储,以查看将哪些代码保留在数组上。
@huynhjl FWIW,我尝试使用JProfiler和MAT来分析堆,但是完全无法遍历对匿名函数类的所有引用,等等。Scala确实需要专用工具来处理此类事情。
—
亚伦·诺夫斯特鲁普
如果没有泄漏,只是您在做的事情需要大量增加的内存该怎么办?您只需维护
—
以西结·维克托
var
计数器即可轻松复制zipWithIndex,而无需使用该特定FP构造。
@EzekielVictor我不确定我是否理解此评论。您建议
—
亚伦·诺夫斯特鲁普
Long
每块添加一个索引会将算法从恒定堆空间更改为非恒定堆空间吗?非压缩版本显然使用了恒定的堆空间,因为它可以“处理”您愿意等待的尽可能多的块。