又是性能对比,最近跟性能较上劲了。

 

产品中需要用到数学表达式,表达式不复杂,但是对性能要求比较高。选用了一些常用的表达式引擎计算方案,包含:java脚本引擎(javax/script)、groovy脚本引擎、Expression4j、Fel表达式引擎。

 

其中java脚本引擎使用了解释执行和编译执行两种方式、groovy脚本只采用了编译执行(解释执行太慢)、Fel采用了静态参数和动态参数两种方式。以下为测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
public  class  ExpressionTest  extends  BaseTest {
 
     private  int  count  100000 ;
     
     //javax的编译执行,效率比解释执行略高?为什么才略高??
     @Test
     public  void  testCompiledJsScript()  throws  Throwable {
         javax.script.ScriptEngine se =  new  ScriptEngineManager().getEngineByName( "js" );
         Compilable ce = (Compilable) se;
         CompiledScript cs = ce.compile( "a*b*c" );
         Bindings bindings = se.createBindings();
         bindings.put( "a" 3600 );
         bindings.put( "b" 14 );
         bindings.put( "c" 4 );
         long  start = System.currentTimeMillis();
         for ( int  i =  0 ; i <  count ; i++) {
             cs.eval(bindings);
         }
         System.out. println (System.currentTimeMillis() - start);
     }
 
     //javax script解释执行
     @Test
     public  void  testJsScript()  throws  Throwable {
         javax.script.ScriptEngine se =  new  ScriptEngineManager().getEngineByName( "js" );
         Bindings bindings = se.createBindings();
         bindings.put( "a" 3600 );
         bindings.put( "b" 14 );
         bindings.put( "c" 4 );
         long  start = System.currentTimeMillis();
         for ( int  i =  0 ; i <  count ; i++) {
             se.eval( "a*b*c" , bindings);
         }
         System.out. println (System.currentTimeMillis() - start);
     }
 
     //groovy的编译执行
     @Test
     public  void  testGroovy() {
         //这里的ScriptEngine和GroovyScriptEngine是自己编写的类,不是原生的
         ScriptEngine se = this.getBean(GroovyScriptEngine. class );
         Map<String, Object> paramMap =  new  HashMap<String, Object>();
         paramMap.put( "param" 5 );
         //ScriptEngine首次执行会缓存编译后的脚本,这里故意先执行一次便于缓存
         se.eval( "3600*34*param" , paramMap);
         
         long  start = System.currentTimeMillis();
         for ( int  i =  0 ; i <  count ; i++) {
             se.eval( "3600*34*param" , paramMap);
         }
         System.out. println (System.currentTimeMillis() - start);
     }
 
     //Expression4J的表达式引擎,这里是通过函数的方式,有点特别
     @Test
     public  void  testExpression4j()  throws  Throwable {
         Expression expression = ExpressionFactory.createExpression( "f(a,b,c)=a*b*c" );
         System.out. println ( "Expression name: "  + expression.getName());
 
         System.out. println ( "Expression parameters: "  + expression.getParameters());
 
         MathematicalElement element_a = NumberFactory.createReal( 3600 );
         MathematicalElement element_b = NumberFactory.createReal( 34 );
         MathematicalElement element_c = NumberFactory.createReal( 5 );
         Parameters parameters = ExpressionFactory.createParameters();
         parameters.addParameter( "a" , element_a);
         parameters.addParameter( "b" , element_b);
         parameters.addParameter( "c" , element_c);
         long  start = System.currentTimeMillis();
         for ( int  i =  0 ; i <  count ; i++) {
             expression.evaluate(parameters);
         }
         System.out. println (System.currentTimeMillis() - start);
     }
 
     //fel的表达式引擎(静态参数,同上面)
     @Test
     public  void  felTest() {
         FelEngine e = FelEngine.instance;
         final FelContext ctx = e.getContext();
         ctx.set( "a" 3600 );
         ctx.set( "b" 14 );
         ctx.set( "c" 5 );
         com.greenpineyu.fel.Expression exp = e.compile( "a*b*c" , ctx);
         long  start = System.currentTimeMillis();
         Object eval =  null ;
         for ( int  i =  0 ; i <  count ; i++) {
             eval = exp.eval(ctx);
         }
         System.out. println (System.currentTimeMillis() - start);
         System.out. println (eval);
     }
 
     //fel表达式引擎(动态参数,这里动态参数的产生和变量改变都会消耗时间,因此这个测试时间不准确,只是验证对于动态参数的支持)
     @Test
     public  void  felDynaTest() {
         FelEngine e = FelEngine.instance;
         final FelContext ctx = e.getContext();
         ctx.set( "a" 3600 );
         ctx.set( "b" 14 );
         ctx.set( "c" 5 );
         com.greenpineyu.fel.Expression exp = e.compile( "a*b*c" , ctx);
         long  start = System.currentTimeMillis();
         Object eval =  null ;
         Random r =  new  Random();
         for ( int  i =  0 ; i <  count ; i++) {
             ctx.set( "a" , r.nextInt( 10000 ));
             ctx.set( "b" , r.nextInt( 100 ));
             ctx.set( "c" , r.nextInt( 100 ));
             eval = exp.eval(ctx);
         }
         System.out. println (System.currentTimeMillis() - start);
         System.out. println (eval);
     }
 
     public  static  void  main(String[] args)  throws  Throwable {
         ExpressionTest et =  new  ExpressionTest();
         //执行100W次的测试
         et. count  1000000 ;
         et.testCompiledJsScript();
         et.testJsScript();
         et.testExpression4j();
         et.testGroovy();
         et.felTest();
     }
 
}

 

测试结果如下:

表达式引擎 执行时间(毫秒) 备注

java脚本引擎编译后执行

7662

 
java脚本引擎解释执行 10609  
expression4j 578  
groovy编译执行 224  
fel静态参数 19  
fel动态参数 107 该项测试比较不公平,随机数的产生以及参数的变更也会占用一定时间,测试目的只是为了验证是不是存在静态优化,从而导致静态性能远高于动态性能的情况。

 

结论:

从以上性能对比来看(抛开表达式的功能),fel明显占据很大优势,groovy和expression4j也是可以接受的。java脚本引擎的执行偏慢。因此,对于表达式不是很复杂性能要求高的情况下,推荐使用fel或者groovy编译执行的方式。