Scalaz 7 zipWithIndex / grup numaralandırmaları ile bellek sızıntılarını önleme


106

Arka fon

Bu soruda belirtildiği gibi , sabit yığın alanında büyük (yani sınırsız) bir veri akışını işlemek için Scalaz 7 yinelemelerini kullanıyorum.

Kodum şöyle görünüyor:

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))

Sorun

Bir bellek sızıntısı ile karşılaşmış gibi görünüyorum, ancak Scalaz / FP ile hatanın Scalaz'da mı yoksa kodumda mı olduğunu bilecek kadar aşina değilim. Sezgisel olarak, bu kodun sadece (sırasıyla) P çarpı Chunk-boyut alanı gerektirmesini bekliyorum .

Not: Bir ile karşılaşılan benzer bir soru buldum OutOfMemoryError, ancak kodum kullanılmıyor consume.

Test yapmak

Sorunu izole etmek için bazı testler yaptım. Özetlemek gerekirse, sızıntı yalnızca her ikisi de kullanıldığında zipWithIndexve groupkullanıldığında ortaya çıkmaktadır .

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

Testler için kod:

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
}

Sorular

  • Hata kodumda mı?
  • Bunun sabit yığın alanında çalışmasını nasıl sağlayabilirim?


1
Hiç eğlenceli olmayacak, ancak -XX:+HeapDumpOnOutOfMemoryErroreclipse MAT eclipse.org/mat ile çöplüğü analiz etmeyi deneyebilirsiniz. dizileri hangi kod satırının tuttuğunu görmek için .
huynhjl

10
@huynhjl FWIW, hem JProfiler hem de MAT ile yığını analiz etmeye çalıştım, ancak anonim işlev sınıflarına vb. yapılan tüm referansları gözden geçiremedim. Scala gerçekten bu tür şeyler için özel araçlara ihtiyaç duyuyor.
Aaron Novstrup

Ya sızıntı yoksa ve yaptığınız şey çılgınca artan miktarda bellek gerektiriyorsa? ZipWithIndex'i, ilerledikçe bir varsayacı koruyarak o belirli FP yapısı olmadan kolayca çoğaltabilirsiniz .
Ezekiel Victor

@EzekielVictor Yorumu anladığımdan emin değilim. LongÖbek başına tek bir dizin eklemenin algoritmayı sabitten sabit olmayan yığın uzayına değiştireceğini mi düşünüyorsunuz? Sıkıştırılmayan sürüm açıkça sabit yığın alanı kullanır çünkü beklemek istediğiniz kadar çok parçayı "işleyebilir".
Aaron Novstrup

Yanıtlar:


4

Bu, eski iterateeAPI ile sıkışıp kalan herkes için biraz teselli olacak , ancak yakın zamanda scalaz-stream API'sine karşı eşdeğer bir testin geçtiğini doğruladım . Bu, değiştirilmesi amaçlanan daha yeni bir akış işleme API'sidir iteratee.

Tamlık için test kodu şu şekildedir:

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

Bu, nparametre için herhangi bir değerle çalışmalıdır (yeterince uzun süre beklemeye istekli olmanız koşuluyla) - 2 ^ 14 32MiB dizisi ile test ettim (yani, zaman içinde ayrılmış toplam yarım TiB bellek).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.