Antlr4“;原始的;递归

Antlr4“;原始的;递归,antlr,antlr4,Antlr,Antlr4,接下来,我试图在我相当复杂的语法中减少左递归。据我所知,递归的非原始形式可能导致内存和处理时间方面的性能问题 所以我试图在语法中重构这些规则,只使用“原始”递归。当然,这篇博文是我唯一一次看到关于Antlr的短语“原始”递归。所以我只是在猜测它的意义/意图。在我看来,这意味着一个规则,它将自己称为lhs,最多只针对一个规则分支。对吗 目前,我有一个表达规则,如: expression : expression DOUBLE_PIPE expression # Concat

接下来,我试图在我相当复杂的语法中减少左递归。据我所知,递归的非原始形式可能导致内存和处理时间方面的性能问题

所以我试图在语法中重构这些规则,只使用“原始”递归。当然,这篇博文是我唯一一次看到关于Antlr的短语“原始”递归。所以我只是在猜测它的意义/意图。在我看来,这意味着一个规则,它将自己称为lhs,最多只针对一个规则分支。对吗

目前,我有一个表达规则,如:

expression
    : expression DOUBLE_PIPE expression         # ConcatenationExpression
    | expression PLUS expression                # AdditionExpression
    | expression MINUS expression               # SubtractionExpression
    | expression ASTERISK expression            # MultiplicationExpression
    | expression SLASH expression               # DivisionExpression
    | expression PERCENT expression             # ModuloExpression
    ...
    ;
包含相当多的子规则,这些子规则也引用
表达式。但是这些是唯一具有直接递归的

如果我理解正确,将这些重构为“原始”递归将类似于:

expression
    : binaryOpExpression                        # BinaryOpExpression
    ...
    ;

binaryOpExpression
    : expression DOUBLE_PIPE expression         # ConcatenationExpression
    | expression PLUS expression                # AdditionExpression
    | expression MINUS expression               # SubtractionExpression
    | expression ASTERISK expression            # MultiplicationExpression
    | expression SLASH expression               # DivisionExpression
    | expression PERCENT expression             # ModuloExpression
    ;
首先,这是正确的重构吗

第二,这真的有助于提高绩效吗?在一天结束时,它仍然是相同的决定,所以我并不真正理解这如何有助于提高性能(除了可能产生更少的ATNConfig对象)

感谢

在本文中,我以前没有听说过“原始递归”,作者可能只是想在ANTLR4中命名一种特定形式的递归

事实上,ANTLR4中有3种相关的递归形式:

  • 直接左递归:从规则中的第一个规则引用递归(到同一规则)。例如:
    a:ab | c
  • 间接左递归:不是直接来自同一规则的左递归。例如:
    a:b | c;b:c|d;c:a | e(ANTLR4中不允许)
  • 右递归:规则中的任何其他递归。例如:
    a:ba | c。然而,“右递归”这个名称仅在二进制表达式的情况下是正确的,但通常用于区分左递归
话虽如此,很明显您的重写是错误的,因为它将创建间接的左递归,而ANLTR4不支持这种递归。直接左递归通常不是问题(从内存或性能的角度来看),因为ANTLR4将它们转换为非递归ATN规则图

可能出现问题的是正确的递归,因为它们是通过代码递归(运行时的递归函数调用)实现的,这可能会耗尽CPU堆栈。我见过一些大表达式无法在单独的线程中解析的情况,因为我无法将线程堆栈大小设置为更大的值(主线程堆栈大小通常可以通过链接器设置进行调整)


对于后一种情况,我发现唯一有用的解决方案是减少语法中相互调用的解析器规则的数量。当然,将某些表达式元素放入不同的规则(例如
和expression
或expression
位表达式
等)是结构、可读性等方面的问题,但这可能会导致相当深的调用堆栈,这可能会耗尽CPU堆栈和/或需要大量时间来处理它们。

谢谢您的回复!为了确保我能理解,你是说规则是“好的”?背景是,我试图弄明白为什么这个AntlrV4语法似乎比我们之前的AntlrV2语法慢得多。是的,苹果/橙子,但我很惊讶性能变慢了。分析显示创建的许多ATNConfig对象。研究表明,这可能是因为太多的决策点可能来自左递归。有什么想法吗?也许这是另一个问题?ATNConfig实例在预测阶段创建,然后缓存。这就是通常所说的热身阶段。通过ATN的每条路径只发生一次。因此,在测量解析性能之前,先进行一次初始运行,让运行时创建缓存,然后使用相同的输入进行第二次解析运行,您可以对其进行测量。正常解析运行与初始解析运行之间很容易存在1:10的关系。是的,语法规则是完全正确的,因为它是自动转换的(正如我在回答中已经提到的)。该语法用于查询语言,因此每次解析都会进行预测。奇怪的是,我们无法确定解析的结果。因此,在整个JMH运行过程中,实际上应该有一个解析。然而ATNConfig对象仍然是最大的内存分配压力之一。这是非常令人担忧的,我收回。。。ATNConfig参考的创建通常在预热期间提前进行。