前言:
  规则引擎中, 往往涉及到多个条件构成了复杂布尔表达式的计算. 对于这类布尔表达式, 一是动态可变的(取决于运营人员的设定), 二是其表达式往往很复杂. 如何快速的计算其表达式的值, 该系列文章将以两种方式, Antlr4动态生成AST(抽象语法树), 以及Groovy动态编译的方式来对比评估, 看看哪种方式性能更优, 以及各自的优缺点. 本篇文章将侧重于Groovy的实现思路.

模型简化:
  每个规则可以理解为多个条件构建的复杂布尔表达式, 而条件本身涉及不同的变量和阈值(常量), 以及中间的操作符(>=, >, <, <=, !=, =). 
  比如某个具体的规则:

rule = expr1 && (expr2 || expr3) || expr4

  而其具体条件expr1/expr2/expr3/expr4如下:

expr1 => var1 >= 20
expr2 => var2 != 10
expr3 => var3 < 3.0
expr4 => var4 = true

  为了简化评估, 我们简单设定每个条件就是一个布尔变量(bool). 这样每个规则rule就可以理解为多个布尔变量, 通过&&和||组合的表达式了, 简单描述为:

rule = 1 && (2 || 3) || 4

  数字N(1,2,...)为具体的布尔变量, 类似这样的简化模型, 方便性能评估.

Groovy实现:
  先配置maven的依赖.

        <dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.13</version>
</dependency>

  然后编写Groovy脚本的执行工具类:

package com.dsl.perfs;

import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.runtime.InvokerHelper; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; public class GroovyShellUtils { private static ConcurrentHashMap<String, Class> scriptClassMap = new ConcurrentHashMap(); public static <T> T execExpr(String expr, Map<String, Object> params, Class<T> returnType) {
if(expr == null || expr.length() == 0) {
return null;
} else {
Object result = null; try {
Class e = parseClass(expr);
result = InvokerHelper.createScript(e, new Binding(params)).run();
return (T)result;
} catch (Exception var5) {
return null;
}
}
} public static Class parseClass(String scriptText) throws CompilationFailedException {
String key = keyGen(scriptText);
Class value = (Class)scriptClassMap.get(key);
if(value != null) {
return value;
} else {
synchronized(scriptText.intern()) {
if(scriptClassMap.get(key) == null) {
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
Class scriptClass = groovyClassLoader.parseClass(scriptText);
scriptClassMap.put(key, scriptClass);
return scriptClass;
}
} return (Class)scriptClassMap.get(key);
}
} private static String keyGen(String script) {
return String.valueOf(script.intern().hashCode());
}
}

  具体执行时, 采用一个trick的方式, 将数值变量化(统一添加变量名前缀).
  比如把表达式:

 &&  ||  ||  && ( || )

  转化为

t1 && t2 || t3 || t4 && (t5 || t6)

测试评估:
  具体的测试代码为:

package com.dsl.comp;

import com.dsl.perfs.GroovyShellUtils;

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; String nboolExpr = boolExpr;
nboolExpr = nboolExpr.replace("1", "t1");
nboolExpr = nboolExpr.replace("2", "t2");
nboolExpr = nboolExpr.replace("3", "t3");
nboolExpr = nboolExpr.replace("4", "t4");
nboolExpr = nboolExpr.replace("5", "t5");
nboolExpr = nboolExpr.replace("6", "t6"); long beg = System.currentTimeMillis();
random.setSeed(randomSeed);
for ( int i = 0; i <= iterNums; i++) {
Map<String, Object> params = new TreeMap<>();
params.put("t1", random.nextBoolean());
params.put("t2", random.nextBoolean());
params.put("t3", random.nextBoolean());
params.put("t4", random.nextBoolean());
params.put("t5", random.nextBoolean());
params.put("t6", random.nextBoolean()); GroovyShellUtils.execExpr(nboolExpr, params, Boolean.class);
}
long end = System.currentTimeMillis();
System.out.println(String.format("total consume: %dms", end - beg)); } }

  测试结果如下:

total consume: 1039ms

  和上篇Antlr4方案的测试结果755ms, 1039ms相对慢一些, 但总结而言差不多, 事实上, 无论采用哪种方案, 对于具体的线上服务而言, 其永远不是主要的性能瓶颈.

优缺点分析:
  从性能结果上看, Antlr4动态解析的方案有一定的优势. 另一方面, 采用Groovy的方案, 对应的表示式会生成一个对应的Class类, 表达式越多, 生成的Class越多, 对方法区的消耗也不小. 由于JIT的存在, 会将热点的代码编译生成native code, 用于代码的加速执行. 但是该native code区域的空间相对较小, 满了会影响性能.
  但是从灵活性和场景适用范围而言, Groovy方案几乎完胜, Antlr4的编码成本太高, 尤其是面对复杂的逻辑时.

总结:
  本文也是借助复杂布尔表达式的评估, 来简单比较下Antlr方案和Groovy方案的差异. 条条大路通罗马, 其实那个方案都合理.

复杂的动态布尔表达式性能评估(2)--Groovy实现的更多相关文章

  1. 复杂的动态布尔表达式性能评估(1)--Antlr4实现

    前言: 规则引擎中, 往往涉及到多个条件构成了复杂布尔表达式的计算. 对于这类布尔表达式, 一是动态可变的(取决于运营人员的设定), 二是其表达式往往很复杂. 如何快速的计算其表达式的值, 该系列文章 ...

  2. [转载]Linux服务器性能评估与优化

    转载自:Linux服务器性能评估与优化 一.影响Linux服务器性能的因素 1. 操作系统级 CPU 内存 磁盘I/O带宽 网络I/O带宽 2.        程序应用级 二.系统性能评估标准 影响性 ...

  3. Linux服务器性能评估

    一.影响Linux服务器性能的因素 1. 操作系统级 CPU 内存 磁盘I/O带宽 网络I/O带宽 2. 程序应用级 二.系统性能评估标准 影响性能因素 影响性能因素 评判标准 好 坏 糟糕 CPU ...

  4. Linux服务器性能评估与优化--转

    http://www.itlearner.com/article/4553 一.影响Linux服务器性能的因素 1. 操作系统级 Ø       CPU Ø       内存 Ø       磁盘I/ ...

  5. Linux服务器性能评估与优化(一)

    网络内容总结(感谢原创) 1.前言简介 一.影响Linux服务器性能的因素   1. 操作系统级         性能调优是找出系统瓶颈并消除这些瓶颈的过程. 很多系统管理员认为性能调优仅仅是调整一下 ...

  6. [转]网络性能评估工具Iperf详解(可测丢包率)

    原文链接:安全运维之:网络性能评估工具Iperf详解:http://os.51cto.com/art/201410/454889.htm 参考博文:http://linoxide.com/monito ...

  7. 转贴---Linux服务器性能评估

    http://fuliang.iteye.com/blog/1024360 http://unixhelp.ed.ac.uk/CGI/man-cgi?vmstat ------------------ ...

  8. 目标检测模型的性能评估--MAP(Mean Average Precision)

    目标检测模型中性能评估的几个重要参数有精确度,精确度和召回率.本文中我们将讨论一个常用的度量指标:均值平均精度,即MAP. 在二元分类中,精确度和召回率是一个简单直观的统计量,但是在目标检测中有所不同 ...

  9. 180807-Quick-Task 动态脚本支持框架之Groovy脚本加载执行

    Quick-Task 动态脚本支持框架之Groovy脚本加载执行 上一篇简答说了如何判断有任务动态添加.删除或更新,归于一点就是监听文件的变化,判断目录下的Groovy文件是否有新增删除和改变,从而判 ...

随机推荐

  1. servlet初认知(持续更新中)

    一:前言: 一个Servlet程序其实就是一个实现了Java特殊接口的类,它由支持Servlet(具有Servlet引擎)的WEB服务器调用和启动运行.一个Servlet程序负责处理它对应的一个或者多 ...

  2. CP-ABE的使用

    参考: http://acsc.cs.utexas.edu/cpabe/tutorial.html http://acsc.cs.utexas.edu/cpabe/ 事先先配置好cp-abe:http ...

  3. Let's Encrypt 免费通配符 SSL 证书申请教程——但是也需要email,域名所有权等,如果是黑产用的话会这样用吗?会不会暴露自己身份???

    Let's Encrypt 免费通配符 SSL 证书申请教程 from:https://blog.csdn.net/English0523/article/details/79608464 2018 ...

  4. git通过diff文件,合并未上传代码库代码

    今天有段代码需要从别人的机器上同步到本地,但是这段代码还没上库,所以要么将这部分代码打包传过来,或者,用下面的办法. 由于代码修改涉及多个文件,打包搞过来确实比较麻烦,在网上找了下,发现可以用git ...

  5. EventBus简单封装

    前言 以前每个页面与每个页面业务逻辑传递让你不知所措,一个又一个接口回调,让你晕头转向,一个又一个参数让你混乱不堪.EventBus一个耦合度低的让你害怕的框架. 什么是EventBus EventB ...

  6. Java Web(三) Servlet会话管理

    会话跟踪 什么是会话? 可简单理解为,用户打开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭服务器,整个过程称为一个会话.从特定客户端到服务器的一系列请求称为会话.记录会话信息的技术称 ...

  7. caffe的一些概念理解

    有一天,师姐问我,epoch和iteration有什么区别?我一时语塞,竟然遍寻百度而不得,最后在stackoverflow上找到一个我认为比较靠谱的答案,虽然它不是最高票,但是是最好理解的,深得我心 ...

  8. MySQL 占用cpu 100%

    目前的线上数据库,分为主从两个库,从库用来做比较耗时的数据统计分析. 今天top了一下从库服务器,发现mysqld 在很长一段时间都占用105% cpu,一开始以为是从库在处理主库的binlog. 两 ...

  9. Scrapy结构

    http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html scrapy 使用Twisted 这个异步网络库来处理网络通信,使用pyt ...

  10. 怎样关掉 ubuntu 中的 System Program Problem Detected 提示框

    怎样关掉 ubuntu 中的 System Program Problem Detected 提示框 方法如下:sudo gedit /etc/default/apport  打开该文件如下:# se ...