使用Scalaz 7 zipWithIndex/group枚举避免内存泄漏
背景 如中所述,我正在使用Scalaz 7迭代者在恒定堆空间中处理一个大的(即无界的)数据流 我的代码如下所示:使用Scalaz 7 zipWithIndex/group枚举避免内存泄漏,scala,scalaz,iterate,Scala,Scalaz,Iterate,背景 如中所述,我正在使用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[Ve
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乘以块大小的空间
注意:我发现其中遇到了outofmemory错误
,但我的代码没有使用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
问题
- 我的代码中有bug吗
- 如何在恒定的堆空间中工作
对于那些使用较旧的iteratee
API的人来说,这算不上什么安慰,但我最近验证了一个与之相当的测试通过了。这是一个较新的流处理API,旨在取代iteratee
为完整起见,以下是测试代码:
// create a stream containing `n` arrays with `sz` Ints in each one
def streamArrs(sz: Int, n: Int): Process[Task, Array[Int]] =
(Process emit Array.fill(sz)(0)).repeat take n
(streamArrs(1 << 25, 1 << 14).zipWithIndex
pipe process1.chunk(4)
pipe process1.fold(0L) {
(c, vs) => c + vs.map(_._1.length.toLong).sum
}).runLast.run
//创建一个包含`n`数组的流,每个数组中都有`sz`int
def streamArrs(sz:Int,n:Int):进程[任务,数组[Int]]=
(进程emit Array.fill(sz)(0))。重复take n
(streamArrs)(1)我最后将此报告为。这不会有任何乐趣,但您可以尝试-XX:+HeapDumpOnOutOfMemoryError
并使用eclipse MAT分析转储,以查看哪些代码行保留在数组上。@huynhjl FWIW,我尝试使用JProfiler和MAT分析堆,但完全无法遍历对anon的所有引用Imous函数类等。Scala真的需要专门的工具来完成这类工作。如果没有泄漏,只是因为您正在做的事情需要大量的内存,怎么办?您可以轻松地复制zipWithIndex,而不需要特定的FP构造,只需在运行时维护一个var
计数器。@EzekielVictor我不确定我是否理解这个评论。你的意思是,在每个块中添加一个Long
索引会将算法从常量堆空间更改为非常量堆空间?非压缩版本显然使用常量堆空间,因为它可以“处理”你愿意等待的任意多个块。
// create a stream containing `n` arrays with `sz` Ints in each one
def streamArrs(sz: Int, n: Int): Process[Task, Array[Int]] =
(Process emit Array.fill(sz)(0)).repeat take n
(streamArrs(1 << 25, 1 << 14).zipWithIndex
pipe process1.chunk(4)
pipe process1.fold(0L) {
(c, vs) => c + vs.map(_._1.length.toLong).sum
}).runLast.run