Scalaz 7 zipWithIndex / group 열거로 메모리 누수 방지


106

배경

이 질문 에서 언급했듯이 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.

테스팅

문제를 격리하기 위해 몇 가지 테스트를 실행했습니다. 요약하면, 누출시에만 모두 발생하는 표시 zipWithIndexgroup사용됩니다.

// 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 년

1
재미는 없지만 -XX:+HeapDumpOnOutOfMemoryErroreclipse MAT eclipse.org/mat 로 덤프를 분석 하여 배열에 어떤 코드 줄이 있는지 확인할 수 있습니다.
huynhjl

10
@huynhjl FWIW, JProfiler와 MAT로 힙 분석을 시도했지만 익명 함수 클래스 등에 대한 모든 참조를 완전히 통과 할 수 없었습니다. 스칼라는 정말 이런 종류의 전용 도구가 필요합니다.
Aaron Novstrup 2010

누출이없고 수행중인 작업에 엄청나게 증가하는 메모리 양이 필요하다면 어떨까요? var카운터를 유지하는 것만으로 특정 FP 구조없이 zipWithIndex를 쉽게 복제 할 수 있습니다 .
Ezekiel Victor

@EzekielVictor 댓글을 이해했는지 잘 모르겠습니다. Long청크 당 단일 인덱스 를 추가 하면 알고리즘이 상수에서 상수가 아닌 힙 공간으로 변경 된다는 것을 제안하고 있습니까? 압축되지 않은 버전은 기다릴만큼 많은 청크를 "처리"할 수 있기 때문에 일정한 힙 공간을 사용합니다.
Aaron Novstrup 2014 년

답변:


4

이것은 이전 iterateeAPI를 사용하는 사람에게는 별다른 위로가되지 않을 것이지만 최근에 동일한 테스트가 scalaz-stream 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매개 변수에 대한 모든 값과 함께 작동합니다 (충분히 기다릴 수있는 경우)-2 ^ 14 32MiB 배열 (즉, 시간에 따라 할당 된 총 메모리의 절반 TiB)으로 테스트했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.