表达式引擎性能比较
表达式引擎选型
选择3种主要的表达式引擎进行性能对比,从而选择最优表达式引擎和最优方案。常用的3中规则引擎:QLExpress、MEVL、JUEL。
性能测试
性能测试维度:
- 表达式引擎维度:主要采用了3种引擎的5种实现方式,即
QLExpress 使用缓存
;mvel 不编译
;mvel 先编译
;mvel 先编译,且指定输入值类型
;juel 指定输入值类型
。注:QLExpress 不使用缓存
的话,性能非常低,就不再进行对比测试。 - 表达式维度:主要采用了3种类型的表达式,后续的大多数需求,都属于这3种方式。即:
- 最常见的场景(多个条件进行and操作):
a<100 && b>=100 && c<=123
, - 包含特殊的操作(contains):
a<100 && b>=100 && c<=123 && stringList.contains(str)
- 一个稍微复杂一点的语法树:
a>1 && ((b>1 || c<1) || (a>1 && b<1 && c>1))
- 最常见的场景(多个条件进行and操作):
测试方式
10次循环,每次循环执行这5种方式10万次,即每种方式执行100万次。为了保证一定的差异性,变量赋值的时候,采用变化的值。代码详见附录。
执行结果:
对结果进行可视化,很显然Mvel不编译
方式耗时最高。
为了方便查看,将Mvel不编译
方式过滤掉,得到下图。得到以下结论:
- 性能高低顺序为:
mvel 先编译,且指定输入值类型
>mvel 先编译
>juel 指定输入值类型
>QLExpress 使用缓存
>mvel 不编译
。 juel 指定输入值类型
性能也可以接受,但是在使用了contains
操作后,性能明显降低很多。
结论
mvel 先编译,且指定输入值类型
方式性能为最优,采用该方式作为表达式引擎的实现方式。但是,采用自己实现一层缓存:用来缓存预编译的结果,同时,采用2小时无访问,则更新缓存的策略。缓存使用Guava Cache实现。
注: 1. QLExpress自身实现了缓存机制,如果性能要求没那么高的话,也可以采用该方式。 2. 如果规则非常多,比如到了亿级别的规模,并且基本不复用规则的话,就没有必要先编译并缓存了。或者调整缓存策略。
附录
机器配置
1 | MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports) |
表达式引擎版本:
1 | <!-- https://mvnrepository.com/artifact/org.mvel/mvel2 --> |
Java代码
详见: https://github.com/DanielJyc/expression-language-compare/blob/master/java/src/Test.java