หลีกเลี่ยงการรั่วไหลของหน่วยความจำด้วย Scalaz 7 zipWithIndex / group enumeratees


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 / 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
Aaron Novstrup

1
มันจะไม่สนุก แต่คุณสามารถลอง-XX:+HeapDumpOnOutOfMemoryErrorวิเคราะห์ดัมพ์ด้วย eclipse MAT eclipse.org/matเพื่อดูว่าโค้ดที่อยู่ในอาร์เรย์เป็นบรรทัดใด
huynhjl

10
@huynhjl FWIW ฉันพยายามวิเคราะห์ฮีปด้วยทั้ง JProfiler และ MAT แต่ไม่สามารถลุยผ่านการอ้างอิงทั้งหมดไปยังคลาสฟังก์ชันที่ไม่ระบุตัวตนได้อย่างสมบูรณ์ ฯลฯ Scala ต้องการเครื่องมือเฉพาะสำหรับสิ่งนี้จริงๆ
Aaron Novstrup

จะเกิดอะไรขึ้นถ้าไม่มีการรั่วไหลและสิ่งที่คุณกำลังทำอยู่นั้นต้องการหน่วยความจำที่เพิ่มขึ้นอย่างมาก? คุณสามารถจำลอง zipWithIndex ได้อย่างง่ายดายโดยไม่ต้องสร้าง FP โดยเฉพาะเพียงแค่รักษาตัวvarนับขณะที่คุณไป
Ezekiel Victor

@EzekielVictor ฉันไม่แน่ใจว่าฉันเข้าใจความคิดเห็น คุณกำลังแนะนำว่าการเพิ่มLongดัชนีเดียวต่อกลุ่มจะเปลี่ยนอัลกอริทึมจากค่าคงที่เป็นพื้นที่ฮีปที่ไม่คงที่หรือไม่ เวอร์ชันที่ไม่บีบอัดจะใช้พื้นที่ฮีปคงที่อย่างชัดเจนเนื่องจากสามารถ "ประมวลผล" ชิ้นส่วนได้มากเท่าที่คุณต้องการรอ
Aaron Novstrup

คำตอบ:


4

นี้จะมาเป็นปลอบใจเล็ก ๆ น้อย ๆ สำหรับทุกคนที่ติดอยู่กับพี่iterateeAPI แต่ฉันเพิ่งสอบว่าการทดสอบเทียบเท่าผ่านกับscalaz สตรีม 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.