我有一个处理 AND 和 OR 表达式的antlr规则。它看起来像这样:
expr : expr 'AND' expr
| expr 'OR' expr
| 'a' | 'b' | 'c' | 'd';
这导致解析树非常深。例如。如果你有
a AND b AND c AND d
产生如下树:
expr
/ | \
expr AND d
/ | \
expr AND c
/ | \
a AND b
评估这可能会非常深入和昂贵,所以我想添加一个优化。我想一起处理多个连续的AND表达式(类似于OR-s)。
所以我想做这样的事情:
expr : expr ('AND' expr)+
| expr ('OR' expr)+
| 'a' | 'b' | 'c' | 'd';
我认为这将为序列中的所有AND-s生成一个节点。
然而,当我这样做时,antlr仍然选择生成递归树。我想这是因为规则含糊不清。关于如何让它变得更平坦的任何想法?这是规则的排序还是类似的问题?我关心深度的原因是由于深度递归导致的性能影响。
答案 0 :(得分:2)
递归是否会产生实际的,经过测量的性能问题。如果是这样,您能否量化您正在处理的递归范围。 Antlr通常非常擅长处理递归,所以如果遇到真正的性能问题,可能是由于Antlr中存在更深层次的问题。 Ter和Sam将需要重现它才能处理它。
也就是说,规则rhs上的每个expr
实例都会创建一个递归。分组和限制实例
expr : expr ('AND' | 'OR') expr
| 'a' | 'b' | 'c' | 'd'
;
不会更改满足规则所需的递归次数 - 这取决于正在处理的数据。
如果您的性能问题实际上源于很多深度递归规则失败,那么可能会获得更大收益的是重构您的语法以使规则更快失败或限制数量规则(或子规则)可能适用于任何给定的数据序列。
根据目前提供的信息,究竟有多难说。
<强>更新强>
为了澄清,Antlr中的recursive
规则调用与其他规则调用的实现方式不同;它不是通过递归Java方法调用实现的。
Antlr的LL(*)算法隐含地是针对预先计算的有效状态网络运行的顺序路径求解器。检查点信息保留在每个决策点,包括规则调用,用于回溯。捕获规则调用状态转换所需的检查点数据相对简单,对是否不敏感;目标节点是否代表基于语法的recursive
调用。性能主要与规则节点评估的数量相关,尤其包括所有尝试并最终失败的规则子路径的评估。
这就是为什么将recursive
规则展开到多个规则中不太可能提高性能的原因。如果他们共同实现相同的递归函数,那么最多,针对相同输入的执行将需要相同数量的规则调用。在最坏的情况下,规则的非最小表达式将需要更多的规则调用,并且可能会产生更多的内部检查点(每个*和+括号都被隐式保护)。
假设您的语法被最佳地最小化,并且20+表达式规则的顺序在统计上针对您的源数据进行了优化,您可以通过仅执行实际的实际子树来进一步优化匹配下一个源数据序列。
提前统计预测正确的子树规则。鉴于您评估解析树的次数,您应该能够相当快速地将源数据序列的一些简单计算签名与最终匹配的子树相关联。或者至少决定更合适的子树排序来尝试。
根据目前提供的信息,很难说出签名功能应该是什么,或者成本效益是否为正。实际上取决于源数据的性质。
答案 1 :(得分:1)
如果你有一些像旧语法(C grammar)这样的规则,你可以很容易地做到这一点。
expr: orExpr
;
orExpr: andExpr ('OR' andExpr)*
;
andExpr : primExpr ('AND' primExpr)*
;
primExpr:'a' | 'b' | 'c' | 'd';
WS : ' ' -> skip;
示例文本:
a AND b AND c AND d
结果: