Dividir los iteradores de Scala conduce a problemas de GCoverhead/JavaHeapSpace
Frecuentes
Visto 376 veces
1
Estoy procesando datos de gran tamaño con Scala, por lo que la memoria y el tiempo son un compañero aún más importante de lo que suele ser para mí. Estoy tratando de aumentar la velocidad de alguna evaluación subdividiendo el inicial Iterator[String]
obtenido por getLines
en un archivo fuente grande para hacer una subevaluación en paralelo y fusionar los resultados. Hago esto recursivamente slice
- dividiendo el iterador en dos mitades y recuperando la función recursiva en cada subiterador. Ahora, me pregunto por qué obtengo la excepción GCoverhead o JavaHeapSpace, aunque los elementos "críticos" solo se evalúan una vez antes del paso de recursión (para obtener el tamaño del iterador), pero en mi opinión no en el paso de recursión, porque slice
devuelve un iterador nuevamente (que no es estricto por implementación). El siguiente código (¡reducido!) fallará al aplicarse en un archivo de ~15 g antes de concatenar las sublistas.
yo suelo .duplicate
en cada paso. Busqué la API, el documento de .duplicate
dice "La implementación puede asignar almacenamiento temporal para los elementos iterados por un iterador pero aún no por el otro", pero aún no se ha iterado ningún elemento. ¿Podría alguien darme una pista de lo que está mal allí y cómo resolver este problema? ¡Muchas gracias!
type itType = Iterator[String]
def src = io.Source.fromFile(args(0)).getLines
// recursively divide into equal size blocks in divide&conquer fashion
def getSubItsDC(it: itType, depth: Int = 4) = {
println("Getting length of file..")
val totalSize = src.length
println(totalSize)
def rec(it_rec: itType = it, depth_rec: Int = depth, size: Int = totalSize):
List[itType] = depth_rec match {
case n if n > 0 =>
println(n)
val (it1, it2) = it_rec.duplicate
val newSize = size/2
rec(it1 slice (0,newSize), n-1, newSize) ++
rec(it2 slice (newSize,size), n-1, newSize)
case n if n == 0 => List(it_rec)
}
println("Starting recursion..")
rec()
}
getSubItsDC(src)
En el REPL, el código se ejecuta igualmente rápido con un tamaño arbitrario de iteradores (cuando se codifica el tamaño total), por lo que asumí la pereza correcta.
1 Respuestas
2
Creo que es mejor que uses el itr grouped size
para obtener una Iterator[Iterator[String]]
(a GroupedIterator
):
scala> val itr = (1 to 100000000).iterator grouped 1000000
itr: Iterator[Int]#GroupedIterator[Int] = non-empty iterator
Esto le permitirá fragmentar el procesamiento de partes de su archivo.
Por qué su solución usa demasiada memoria
Duplicar un Iterator
is obviamente una operación que significa que el iterador puede tener que almacenar en caché sus valores calculados. Por ejemplo:
scala> val itr = (1 to 100000000).iterator
itr: Iterator[Int] = non-empty iterator
scala> itr filter (_ % 10000000 == 0) foreach println
10000000
....
100000000
Pero cuando tomo un duplicado:
scala> val (a, b) = (1 to 100000000).iterator.duplicate
a: Iterator[Int] = non-empty iterator
b: Iterator[Int] = non-empty iterator
scala> a filter (_ % 10000000 == 0) foreach println
//oh dear, garbage collecting
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
En este ejemplo, mientras recorro a
, para que b
ser un duplicado, los elementos que a
ha iterado sobre pero cuál b
no tiene, necesita ser almacenado en caché
Respondido el 12 de junio de 12 a las 16:06
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas scala iterator duplicates slice divide or haz tu propia pregunta.
Gracias por tu respuesta, te lo agradezco! No estoy muy seguro de cómo su ejemplo de
.duplicate
explica mi problema de código, porque en realidad nunca itero sobre ningún elemento, ¿verdad? Necesito considerar el uso de.grouped
. Tengo la sensación de que esto no es exactamente lo que estaba buscando, porque devuelve una especie de iterador de listas. Mi método recursivo, por el contrario, estaba destinado a devolver una Lista de iteradores (al revés) para que pueda llamar.par
en esa lista sin ningún esfuerzo adicional. Agrupando primero y llamando.toList.par
causará el mismo bloqueo de memoria. ¿Alguna idea para eso? - Wayne JhukieBueno, tu solución no es recursiva de cola y usa
Iterator.duplicate
. No estoy seguro de por qué un iterador de Listas no es exactamente lo que está buscando. ¿Hay un truco mental Jedi aquí, tal vez? - oxbow_lakesEstaba al tanto de la recursividad sin cola, pero ¿es este el problema? La profundidad de recursión es 4, por lo que generará una lista de máx. talla 16 (espero no equivocarme ahí). Uso duplicado, sí, pero no evalúo ninguno de los elementos, por lo que no se necesita almacenamiento en caché, ¿por qué sigue fallando? Su ejemplo también se ejecuta durante un tiempo, por lo que esto debe significar que realmente solo se almacenan en caché los elementos evaluados, ¿o me equivoco? No puedo usar Iterator[List], porque me gustaría enhebrar en List[Iterator] a través de
par
sin tener los sub-iteradores siendo evaluados antes. VocacióntoList
en Iterator[List] cargaría todos los datos en la memoria. - Wayne JhukieArgh está bien, podría intentarlo
myGroupedIterator.next.par
, déjame probar eso! Lo siento. - Wayne Jhukie