又是性能对比,最近跟性能较上劲了。 产品中需要用到数学表达式,表达式不复杂,但是对性能要求比较高。选用了一些常用的表达式引擎计算方案,包含:java脚本引擎(javax/script)、groovy脚本引擎、Expression4j、Fel表达式引擎。 其中java脚本引擎使用了解释执行和编译执行两种方式、groovy脚本只采用了编译执行(解释执行太慢)、Fel采用了静态参数和动态参数两种方式。以下为测试代码:
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();
    }
 
}
测试结果如下: 企业微信截图_16623466478702
结论:从以上性能对比来看(抛开表达式的功能),fel明显占据很大优势,groovy和expression4j也是可以接受的。java脚本引擎的执行偏慢。因此,对于表达式不是很复杂性能要求高的情况下,推荐使用fel或者groovy编译执行的方式。