复杂的动态布尔表达式性能评估(1)--Antlr4实现
前言:
规则引擎中, 往往涉及到多个条件构成了复杂布尔表达式的计算. 对于这类布尔表达式, 一是动态可变的(取决于运营人员的设定), 二是其表达式往往很复杂. 如何快速的计算其表达式的值, 该系列文章将以两种方式, Antlr4动态生成AST(抽象语法树), 以及Groovy动态编译的方式来对比评估, 看看哪种方式性能更优, 以及各自的优缺点. 本篇文章将侧重于介绍Antlr4的实现思路.
模型简化:
每个规则可以理解为多个条件构建的复杂布尔表达式, 而条件本身涉及不同的变量和阈值(常量), 以及中间的操作符(>=, >, <, <=, !=, =).
比如某个具体的规则:
rule = expr1 && (expr2 || expr3) || expr4
而其具体条件expr1/expr2/expr3/expr4如下:
expr1 => var1 >=
expr2 => var2 !=
expr3 => var3 < 3.0
expr4 => var4 = true
为了简化评估, 我们简单设定每个条件就是一个布尔变量(bool). 这样每个规则rule就可以理解为多个布尔变量, 通过&&和||组合的表达式了, 简单描述为:
rule = 1 && (2 || 3) || 4
数字N(1,2,...)为具体的布尔变量, 类似这样的简化模型, 方便性能评估.
Antlr4构建:
Anltr4是基于LL(K), 以递归下降的方式进行工作. 它能自动完成语法分析和词法分析过程,并生产框架代码.
具体可参阅文章: 利用ANTLR4实现一个简单的四则运算计算器, 作为案列参考.
其实表达式解析比四则混合运算的语法gammar还要简单.
编写EasyDSL.g4文件:
grammar EasyDSL; /** PARSER */
line : expr EOF ;
expr
: '(' expr ')' # parenExpr
| expr '&&' expr # andEpr
| expr '||' expr # orEpr
| ID # identifier
; /** LEXER */
WS : [ \t\n\r]+ -> skip ;
ID : DIGIT+ ;
fragment DIGIT : '0'..'9';
其在idea工程中, 如下所示:

配置pom.xml, 添加dependency和plugin.
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.3</version>
</dependency> </dependencies> <plugins> <build>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.3</version>
<executions>
<execution>
<id>antlr</id>
<goals>
<goal>antlr4</goal>
</goals>
<!--<phase>none</phase>-->
</execution>
</executions>
<configuration>
<outputDirectory>src/main/java</outputDirectory>
<listener>true</listener>
<treatWarningsAsErrors>true</treatWarningsAsErrors>
</configuration>
</plugin>
</plugins>
</build>
具体执行命令
mvn antlr4:antlr4
则生成对应的代码

代码扩展:
Antlr4帮我们构建了基础的词法和语法解析后, 后续工作需要我们自己做些功能扩展.
首先我们定义操作枚举类:
package com.dsl.perfs;
public enum ExprType {
AND,
OR,
ID;
}
然后是具体的节点类:
package com.dsl.perfs;
public class ExprNode {
public ExprType type;
public String id;
public ExprNode left;
public ExprNode right;
public ExprNode(ExprType type, String id, ExprNode left, ExprNode right) {
this.type = type;
this.id = id;
this.left = left;
this.right = right;
}
}
最后重载Listener类, 对这可抽象语法树进行构建.
package com.dsl.perfs; import com.dsl.ast.EasyDSLBaseListener;
import com.dsl.ast.EasyDSLParser;
import org.antlr.v4.runtime.misc.NotNull; import java.util.Stack; public class EasyDSLListener extends EasyDSLBaseListener { private Stack<ExprNode> stacks = new Stack<>(); @Override
public void exitIdentifier(@NotNull EasyDSLParser.IdentifierContext ctx) {
stacks.push(new ExprNode(ExprType.ID, ctx.getText(), null, null));
} @Override
public void exitAndEpr(@NotNull EasyDSLParser.AndEprContext ctx) {
ExprNode right = stacks.pop();
ExprNode left = stacks.pop(); stacks.push(new ExprNode(ExprType.AND, null, left, right));
} @Override
public void exitOrEpr(@NotNull EasyDSLParser.OrEprContext ctx) {
ExprNode right = stacks.pop();
ExprNode left = stacks.pop(); stacks.push(new ExprNode(ExprType.OR, null, left, right));
} @Override
public void exitLine(@NotNull EasyDSLParser.LineContext ctx) {
super.exitLine(ctx);
} @Override
public void exitParenExpr(@NotNull EasyDSLParser.ParenExprContext ctx) {
// DO NOTHING
} public ExprNode getResult() {
return stacks.peek();
} }
以下是工具类, 其具体构建AST, 并进行具体的值评估.
package com.dsl.perfs; import com.dsl.ast.EasyDSLLexer;
import com.dsl.ast.EasyDSLParser;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; public class ExprEvalutorHelper { private static ConcurrentHashMap<String, ExprNode> exprAstClassMap = new ConcurrentHashMap(); public static boolean exec(String expr, Map<String, Boolean> params) {
ExprNode root = exprAstClassMap.get(expr);
if ( root == null ) {
synchronized (expr.intern()) {
if ( root == null ) {
EasyDSLLexer lexer = new EasyDSLLexer(new ANTLRInputStream(expr));
/* 根据lexer 创建token stream */
CommonTokenStream tokens = new CommonTokenStream(lexer);
/* 根据token stream 创建 parser */
EasyDSLParser paser = new EasyDSLParser(tokens);
/* 为parser添加一个监听器 */
EasyDSLListener listener = new EasyDSLListener();
paser.addParseListener(listener);
/* 匹配 line, 监听器会记录结果 */
paser.line(); root = listener.getResult(); exprAstClassMap.put(expr, root);
}
}
} return ExprEvalutorHelper.evalute(root, params);
} public static boolean evalute(ExprNode cur, Map<String, Boolean> params) {
if ( cur.type == ExprType.ID ) {
return params.get(cur.id);
} else {
if ( cur.type == ExprType.AND ) {
boolean leftRes = evalute(cur.left, params);
// *) 剪枝优化
if ( leftRes == false ) return false;
boolean rightRes = evalute(cur.right, params);
return leftRes && rightRes;
} else {
// *) 如果为 OR
boolean leftRes = evalute(cur.left, params);
// *) 剪枝优化
if ( leftRes == true ) return true;
boolean rightRes = evalute(cur.right, params);
return leftRes || rightRes;
}
}
} }
以表达式
&& || || && ( || )
为例, 其最后最后的AST树如下所示:

测试评估:
编写如下测试代码, 来进行性能评估:
package com.dsl.comp; import com.dsl.perfs.ExprEvalutorHelper; import java.util.Map;
import java.util.Random;
import java.util.TreeMap; public class AntlrPerf { public static void main(String[] args) { String boolExpr = "1 && 2 || 3 || 4 && (5 || 6)"; int iterNums = 1000000;
long randomSeed = 10001L;
Random random = new Random(randomSeed); Long beg = System.currentTimeMillis();
for ( int i = 0; i<=iterNums; i++ ) {
Map<String, Boolean> params = new TreeMap<>();
params.put("1", random.nextBoolean());
params.put("2", random.nextBoolean());
params.put("3", random.nextBoolean());
params.put("4", random.nextBoolean());
params.put("5", random.nextBoolean());
params.put("6", random.nextBoolean());
ExprEvalutorHelper.exec(boolExpr, params);
}
long end = System.currentTimeMillis();
System.out.println(String.format("total consume: %dms", end - beg)); } }
测试结果如下:
total consume: 755ms
100万次计算, 累计消耗755ms, 似乎不错. 但是具体的性能好坏, 需要对比, 下篇将使用Groovy方式去实现, 并进行对比.
总结:
文章介绍了Antlr去解析评估复杂布尔表达式的思路, 其性能也相当的客观, 下文将介绍Groovy的方式去评估, 看看两者性能差异, 以及优缺点.
复杂的动态布尔表达式性能评估(1)--Antlr4实现的更多相关文章
- 复杂的动态布尔表达式性能评估(2)--Groovy实现
前言: 规则引擎中, 往往涉及到多个条件构成了复杂布尔表达式的计算. 对于这类布尔表达式, 一是动态可变的(取决于运营人员的设定), 二是其表达式往往很复杂. 如何快速的计算其表达式的值, 该系列文章 ...
- [转载]Linux服务器性能评估与优化
转载自:Linux服务器性能评估与优化 一.影响Linux服务器性能的因素 1. 操作系统级 CPU 内存 磁盘I/O带宽 网络I/O带宽 2. 程序应用级 二.系统性能评估标准 影响性 ...
- Linux服务器性能评估
一.影响Linux服务器性能的因素 1. 操作系统级 CPU 内存 磁盘I/O带宽 网络I/O带宽 2. 程序应用级 二.系统性能评估标准 影响性能因素 影响性能因素 评判标准 好 坏 糟糕 CPU ...
- Linux服务器性能评估与优化--转
http://www.itlearner.com/article/4553 一.影响Linux服务器性能的因素 1. 操作系统级 Ø CPU Ø 内存 Ø 磁盘I/ ...
- Linux服务器性能评估与优化(一)
网络内容总结(感谢原创) 1.前言简介 一.影响Linux服务器性能的因素 1. 操作系统级 性能调优是找出系统瓶颈并消除这些瓶颈的过程. 很多系统管理员认为性能调优仅仅是调整一下 ...
- [转]网络性能评估工具Iperf详解(可测丢包率)
原文链接:安全运维之:网络性能评估工具Iperf详解:http://os.51cto.com/art/201410/454889.htm 参考博文:http://linoxide.com/monito ...
- 转贴---Linux服务器性能评估
http://fuliang.iteye.com/blog/1024360 http://unixhelp.ed.ac.uk/CGI/man-cgi?vmstat ------------------ ...
- 目标检测模型的性能评估--MAP(Mean Average Precision)
目标检测模型中性能评估的几个重要参数有精确度,精确度和召回率.本文中我们将讨论一个常用的度量指标:均值平均精度,即MAP. 在二元分类中,精确度和召回率是一个简单直观的统计量,但是在目标检测中有所不同 ...
- Linux性能评估命令
Linux性能评估工具 https://www.cnblogs.com/dianel/p/10085454.html Linux性能评估工具 目录 介绍 负载:uptime 查看内核的信息: dmes ...
随机推荐
- 十、 持久层框架(MyBatis)
一.基于MyBatis动态SQL语句 1.if标签 实体类Product的字段比较多的时候,为了应付各个字段的查询,那么就需要写多条SQL语句,这样就变得难以维护. 此时,就可以使用MyBatis动态 ...
- IOS中position:fixed弹出框中的input出现光标错位的问题
解决方案是 在弹框出现的时候给body添加fixed <style type="text/css"> body{ position: fixed; width: 100 ...
- 逆袭之旅DAY17.东软实训.Oracle.存储过程
2018-07-13 09:08:36
- nginx:支持https
1.查看nginx模块 nginx -V 注意是大写的V,小写的v是查看版本号的命令. 如果看到with-ssl那就是有的 2.注册ssl证书并下载 免费的ssl证书有: Let's Encrypt ...
- Docker安装websphere(四)
在Docker容器里安装webshpere <!--前提:已经安装好了docker,能够正常使用.--> (1)docker安装websphere(需要账号和密码登录,不挂载数据卷) 获取 ...
- day22 模块_1
核能来袭--模块 1.简单了解模块 2.Collections 3.Time模块 4.functools 一.初识模块 其实之前写的每一个PY文件都是一个模块 还有一些我们一直在使用的模块 buil ...
- JavaScript 操作DOM对象
1)JavaScript 操作DOM對象 1.DOM:是Document Object Model 的缩写,及文档对象模型 2.DOM通常分为三类:DOM Core(核心).HTML-DOM 和 ...
- oracle数据导入导出数据与编码格式不正确
1.导入dmp文件 imp ZHCG/ZHCG@ORCL file=E:\20160902.1007.dmp full=y 2.导出数据 exp system/manager@ORCL file ...
- calc()
width:calc(): cale(a)计算出表达式a的值. e.g: height:cale(100vh-200px):vh,是指CSS中相对长度单位,表示相对视口高度,通常视口长度单位会被分成1 ...
- maven 打包前 Junit 测试
1. 在需要打包前测试的项目中添加依赖 <dependency> <groupId>junit</groupId> <artifactId>junit& ...