배경
이 질문 에서 언급했듯이 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에 있는지 아니면 내 코드에 있는지 알 수있을만큼 Scalaz / FP에 익숙하지 않습니다. 직관적으로이 코드 는 -size 공간의 P 배 ( Chunk
대략 ) 만 필요 합니다.
참고 :에서 발생한 유사한 질문 을 찾았 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의 문제 로보고 했다 .
—
Aaron Novstrup 2013 년
재미는 없지만
—
huynhjl
-XX:+HeapDumpOnOutOfMemoryError
eclipse MAT eclipse.org/mat 로 덤프를 분석 하여 배열에 어떤 코드 줄이 있는지 확인할 수 있습니다.
@huynhjl FWIW, JProfiler와 MAT로 힙 분석을 시도했지만 익명 함수 클래스 등에 대한 모든 참조를 완전히 통과 할 수 없었습니다. 스칼라는 정말 이런 종류의 전용 도구가 필요합니다.
—
Aaron Novstrup 2010
누출이없고 수행중인 작업에 엄청나게 증가하는 메모리 양이 필요하다면 어떨까요?
—
Ezekiel Victor
var
카운터를 유지하는 것만으로 특정 FP 구조없이 zipWithIndex를 쉽게 복제 할 수 있습니다 .
@EzekielVictor 댓글을 이해했는지 잘 모르겠습니다.
—
Aaron Novstrup 2014 년
Long
청크 당 단일 인덱스 를 추가 하면 알고리즘이 상수에서 상수가 아닌 힙 공간으로 변경 된다는 것을 제안하고 있습니까? 압축되지 않은 버전은 기다릴만큼 많은 청크를 "처리"할 수 있기 때문에 일정한 힙 공간을 사용합니다.