为什么在使用ANTLR生成解析器树时会出现OutOfMemoryException?

时间:2014-11-07 20:41:26

标签: c# antlr

我构建了一个“简单”语法来解释一个看起来像json(或xml)的文件。但是,当我尝试解析文件并在树上导航时,我得到System.OutOfMemoryException

输入文件只有108MB,但包含近500万行。

以下是该文件的示例:

(
    :field ("ObjectName"
        :field (
            :field ("{6BF621F9-A0E2-49BB-A86B-3DE4750954F4}")
            :field (Value)
            :field (Value)
            :field (
                :Time ("Sun Jan 26 10:08:33 2014")
                :last_modified_utc (1390730913)
                :By ("Some text")
                :From (localhost)
            )
            :field ("text/text")
            :field (false)
            :field (false)
        )
        :field ()
        :field ()
        :field ()
        :field (0)
        :field (true)
        :field (true)
    )
.
.
.
.
.
)

遵循语法:

grammar Objects;

/*
 * Parser Rules
 */


compileUnit
    : obj
    ;


obj
    : OPEN ID? (field)* CLOSE
    ;

field
    : ':'(ID)? obj
    ;


/*
 * Lexer Rules
 */


OPEN 
    : '(' 
    ;

CLOSE 
    : ')' 
    ;

ID
    : (ALPHA | ALPHA_IN_STRING)
    ;


fragment
INT_ID
    : ('0'..'9')
    ;

fragment
ALPHA_EACH
    : 'A'..'Z' | 'a'..'z' | '_' | INT_ID | '-' | '.' | '@'
    ;

fragment
ALPHA
    : (ALPHA_EACH)+
    ;

fragment
ALPHA_IN_STRING
    : ('"' ( ~[\r\n] )+ '"')
    ;



WS
    // :    ' ' -> channel(HIDDEN)
    : [ \t\r\n]+ -> skip  // skip spaces, tabs, newlines
    ;

解析器:

var input = new Antlr4.Runtime.AntlrInputStream(text);
var lexer = new ObjectsLexer(input);
var tokens = new Antlr4.Runtime.CommonTokenStream(lexer);
var parser = new ObjectsParser(tokens);

// Context for the compileUnit rule
// ERROR: Here I got the error. When start the to build the tree for compileUnit rule
var ctx = parser.compileUnit();


// The following line is not executed
new ObjectsVisitor().Visit(ctx);

在错误行上,我意识到内存增长是指数级的。

1 个答案:

答案 0 :(得分:3)

  • 如果输入为UTF-8编码且主要使用ASCII字符,则转换为UTF-16将需要大约216MB。
  • 每个令牌使用至少48个字节的内存。
  • 解析树中出现的每个标记至少使用20个字节的内存(除了44个)。
  • 解析树中的每个规则节点至少使用36个字节的内存。如果规则有子节点,则最小值为68字节。

上面的数字不包括任何本地,参数,标签或返回值,如果您使用它们,所有这些都存储在树中。

假设每个令牌有4个字符,解析树中有一半令牌,每个解析树节点平均有3个令牌(这里完全是任意值),你得到:

  • 输入:216MB
  • ~2800万令牌:~1281MB
  • 解析树中的~1400万个终端节点:~267MB
  • ~470万个解析树节点:~308MB

这超过2GB内存,并且不计算与ANTLR内部构建的运行时或动态DFA缓存相关的任何开销。您显然需要将应用程序作为64位进程运行,或者减小输入的大小。