Утечка памяти в Scala при использовании парсера - PullRequest
1 голос
/ 20 февраля 2011

Я пытался написать синтаксический анализатор XML, чтобы прочитать XML-дамп Википедии (английский язык, только текущие редакции, около 6,2 ГБ bzipped) и использую парсер Scala 2.8.1 pull.(более 3 миллионов из 10 миллионов статей), но, похоже, постепенно теряет память и в конечном итоге бомбит с ошибкой из кучи.Я увеличил кучу до 1,5 ГБ, и она пошла дальше (почти до конца), но затем я получил (я забыл точное исключение) ошибку, указывающую на то, что сборщик мусора отказывался (тратя большую часть общего ресурса обработки)без особых усилий).

Мой код кажется мне разумным (хотя это еще не идиоматическая функциональная скала), и я не вижу никакого очевидного источника утечек.Я также знаю, что синтаксический анализатор по-прежнему дорабатывается - но я слишком осведомлен о своем собственном невежестве, чтобы назвать это проблемой библиотеки.Я опытный программист на C ++ и Python, но только начинаю работать в Scala, поэтому буду признателен за любые отзывы.

import java.io.{FileInputStream, BufferedInputStream}
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream
import org.apache.hadoop.io.SequenceFile.{createWriter}
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.hadoop.io.Text
import org.apache.hadoop.io.SequenceFile.CompressionType.BLOCK

import scala.io.Source
import scala.xml.pull.{XMLEventReader, EvElemStart, EvElemEnd, EvText}



object Crunch
{
    private def parsePage( parser : XMLEventReader ) : (String, Long, Long, String) =
    {
        var title = ""
        var id = 0
        var revision = 0
        var text = ""
        var done = false
        while ( parser.hasNext && !done )
        {
            parser.next match
            {
                case EvElemStart(_, "title", _, _ ) =>
                {
                    title = getText( parser, "title" )
                }
                /*case EvElemStart(_, "revision", _, _) =>
                {
                    // Need to get the 'id' from revision
                    revision = getText( parser, "revision" ).toInt
                }*/
                case EvElemStart(_, "id", _, _ ) =>
                {
                    id = getText( parser, "id" ).toInt
                }
                case EvElemStart(_, "text", _, _ ) =>
                {
                    text = getText( parser, "text" )
                }
                case EvElemEnd(_, "page") =>
                {
                    done = true
                }
                case _ =>
            }
        }
        return (title, id, revision, text)
    }

    private def getText( parser : XMLEventReader, inTag : String ) : String =
    {
        var fullText = new StringBuffer()
        var done = false
        while ( parser.hasNext && !done )
        {
            parser.next match
            {
                case EvElemEnd(_, tagName ) =>
                {
                    assert( tagName.equalsIgnoreCase(inTag) )
                    done = true
                }
                case EvText( text ) =>
                {
                    fullText.append( text )
                }
                case _ =>
            }
        }
        return fullText.toString()
    }
    def main( args : Array[String] )
    {
        require( args.length == 2 )
        val fin = new FileInputStream( args(0) )
        val in = new BufferedInputStream(fin)
        val decompressor = new BZip2CompressorInputStream(in)

        val runtime = Runtime.getRuntime

        val conf = new Configuration()
        val fs = FileSystem.get(conf)        

        //val writer = createWriter( fs, conf, new Path(args(1)), new Text().getClass(), new Text().getClass(), BLOCK )

        var count = 0
        try
        {
            val source = Source.fromInputStream( decompressor )
            val parser = new XMLEventReader(source)

            while (parser.hasNext)
            {
                parser.next match
                {
                    case EvElemStart(_, "page", attrs, _) =>
                    {
                        val (title, id, revision, text) = parsePage( parser )

                        //writer.append( new Text(title), new Text(text) )

                        count = count + 1
                        if ( count % 100 == 0 )
                        {
                            printf("%s %d (%dMb mem, %dMb free)\n", title, count,
                                (runtime.totalMemory/1024/1024).toInt,
                                (runtime.freeMemory/1024/1024).toInt )
                        }
                    }
                    case _ =>
                }
                // Do something
            }
        }
        finally
        {
            decompressor.close()
            fin.close()
        }

        println( "Finished decompression.")
    }
}

1 Ответ

3 голосов
/ 20 февраля 2011

Было 2 типа проблем с памятью с парсером XML pull, которые были исправлены в транке:

  1. CData и инструкция по обработке элементы, предотвращающие сборку мусора
  2. Элементы с большим количеством дочерних элементов , каждому дочернему элементу требуется немного памяти, в итоге куча исчерпывается.

Первая проблема обычно приводит к очень быстрому недостатку памяти, поэтому маловероятно, что

Оба должны быть исправлены на 2.9.0 ночью, и я бы порекомендовал их использовать. Если вы работаете с проблемами 2.9.0, потому что это транк и может быть нестабильным, вы также можете сделать бэкпорт этих двух патчей, загрузив и скомпилировав локально XMLEventEventReader и MarkupParser, затем упаковать пакет как 00patch.jar, чтобы он приходит перед jar-файлом библиотеки scala и помещает его под $SCALA_HOME/lib вашей установки 2.8.1.

...