又是性能對比,最近跟性能較上勁了。

 

產品中需要用到數學表達式,表達式不復雜,但是對性能要求比較高。選用了一些常用的表達式引擎計算方案,包含: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編譯執行的方式。