测试比较几种动态代码的速度

  • Java原生代码执行
  • 使用janino编译Java代码执行
  • groovy加载类 以反射创建后,直接调用
  • groovy加载类 以反射创建后,反射调用
  • 使用groovy脚本 执行计算
  • 使用groovy+创建类对象调用
  • 阿里的ql表达式计算

6种代码都计算sum(x,y)

jar补充

groovy 与 阿里的ql 坐标

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>2.5.8</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>QLExpress</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>3.1.0</version>
</dependency>

代码部分

接口与实现

定义一个接口
加上2个实现 java 与 groovy
另外4个在HelloRunning类中

HelloIface.java

1
2
3
4
5
package fluffy.mo.rules;

public interface  HelloIface{
    public int sum(int x, int y);
}

HelloJava.java

1
2
3
4
5
6
7
8
package fluffy.mo.rules;

public class HelloJava implements HelloIface {
    @Override
    public int sum(int x, int y) {
        return x + y;
    }
}

gy01.groovy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package fluffy.mo.rules

class HelloGroovy implements HelloIface {
    @Override
    int sum(int x, int y) {
        int result = x + y;
        return result;
    }
}

一般调用

执行5种调用

  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
128
129
130
131
132
package fluffy.mo.rules;

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
import org.codehaus.janino.SimpleCompiler;

import javax.script.CompiledScript;
import javax.script.SimpleBindings;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;

public class HelloRunning {
    HelloIface helloGroovy;
    CompiledScript groovyScript;
    CompiledScript groovyScriptWithObj;
    HelloIface helloNativeJava = new HelloJava();
    HelloIface helloCompiledJava = null;

    public HelloRunning() {
        try {
            Path path = new File("src/main/java/fluffy/mo/rules/gy01.groovy").toPath();
            String sss = Files.readString(path);
            GroovyClassLoader loader = new GroovyClassLoader();
            Class helloGroovyClass = loader.parseClass(sss);

            GroovyScriptEngineImpl scriptEngine = new GroovyScriptEngineImpl();
            String jsObj = sss + "\n HelloIface scriptedHello =new HelloGroovy();\n scriptedHello.sum(xx,yy);";
            String jss = "int sum(int x, int y) {\n" +
                    "    int result = x + y;\n" +
                    "    return result;\n" +
                    "}\n" +
                    "sum(xx,yy)";
            groovyScript = scriptEngine.compile(jss);
            groovyScriptWithObj = scriptEngine.compile(jsObj);

            Object groovyObj = helloGroovyClass.newInstance();
            if (groovyObj instanceof HelloIface) {
                helloGroovy = (HelloIface) groovyObj;
            }

            Object javaObj = loadJavaClass().newInstance();
            if (javaObj instanceof HelloIface) {
                helloCompiledJava = (HelloIface) javaObj;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public HelloIface getHelloGroovy() {
        return helloGroovy;
    }

    public HelloIface getHelloNativeJava() {
        return helloNativeJava;
    }

    public HelloIface getHelloCompiledJava() {
        return helloCompiledJava;
    }

    public CompiledScript getGroovyScript() {
        return groovyScript;
    }

    public CompiledScript getGroovyScriptWithObj() {
        return groovyScriptWithObj;
    }

    public static void main(String[] args) throws Exception {
        HelloRunning store = new HelloRunning();
        HelloIface helloGroovy = store.getHelloGroovy();
        HelloIface helloNativeJava = store.getHelloNativeJava();
        HelloIface helloCompiledJava = store.getHelloCompiledJava();
        CompiledScript groovyScript = store.getGroovyScript();
        CompiledScript groovyScriptWithObj = store.getGroovyScriptWithObj();

        // 原始java
        helloNativeJava.sum(11, 22);
        helloCompiledJava.sum(11, 22);
        // groovy加载的类 直接调用
        helloGroovy.sum(11, 22);
        // groovy加载的类 反射调用
        Method method = sumMethod();
        method.invoke(helloGroovy, 11, 22);
        // groovy直接脚本
        SimpleBindings bindings = new SimpleBindings();
        bindings.put("xx", 1);
        bindings.put("yy", 2);
        groovyScript.eval(bindings);
        // groovy脚本创建对象,并调用
        groovyScriptWithObj.eval(bindings); // 每次调用都会创建 HelloGroovy对象

        // 阿里的ql表达式计算
        ExpressRunner runner = new ExpressRunner();
        DefaultContext<String, Object> context = new DefaultContext<String, Object>();
        context.put("xx", 1);
        context.put("yy", 2);
        String express = "a+b";
        Object r = runner.execute(express, context, null, true, false);
    }

    public static Class<?> loadJavaClass() throws Exception {
        String className = "fluffy.mo.rules.HelloJava02";
        String classSimpleName = "HelloJava02";
        String fileName = classSimpleName + ".java";
        Path path = new File("src/main/java/fluffy/mo/rules/HelloJava.java").toPath();
        String sss = Files.readString(path);
        // 编译后的class放在这个里面
        String strSource = sss.replace("HelloJava", classSimpleName);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(strSource.getBytes());
        ClassLoader cl = new SimpleCompiler(null, inputStream).getClassLoader();
        Class<?> c = cl.loadClass(className);
        return c;
    }

    public static Method sumMethod() {
        Method[] methods = HelloIface.class.getMethods();
        for (Method method : methods) {
            if (method.getName().equals("sum")) {
                return method;
            }
        }
        return null;
    }

}

使用JMH测试

 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
package fluffy.mo.rule_jmh;

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import fluffy.mo.rules.HelloIface;
import fluffy.mo.rules.HelloRunning;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import javax.script.*;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Warmup(iterations = 8, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 8, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyGyBenchmark {
    @Benchmark
    public void testCompiledScript(MyState st, Blackhole blackhole) throws ScriptException {
        blackhole.consume(st.groovyScript.eval(st.bindings));
    }

    @Benchmark
    public void testCompiledScriptWithObj(MyState st, Blackhole blackhole) throws ScriptException {
        blackhole.consume(st.groovyScriptWithObj.eval(st.bindings));
    }

    @Benchmark
    public void testAlibabaQle(MyState st, Blackhole blackhole) throws Exception {
        String express = "xx + yy";
        blackhole.consume(st.runner.execute(express, st.context, null, true, false));
    }

    @Benchmark
    public void testGloaderByReflect(MyState st, Blackhole blackhole) throws Exception {
        blackhole.consume(st.method.invoke(st.helloGroovy, 11, 22));
    }

    @Benchmark
    public void testGloaderByIface(MyState st, Blackhole blackhole) {
        blackhole.consume(st.helloGroovy.sum(11, 22));
    }

    @Benchmark
    public void testNativeJava(MyState st, Blackhole blackhole) {
        blackhole.consume(st.helloNativeJava.sum(11, 22));
    }

    @Benchmark
    public void testCompiledJava(MyState st, Blackhole blackhole) {
        blackhole.consume(st.helloCompiledJava.sum(11, 22));
    }

    @State(Scope.Benchmark)
    public static class MyState {
        HelloIface helloNativeJava;
        HelloIface helloCompiledJava;
        HelloIface helloGroovy;
        Method method = HelloRunning.sumMethod();
        CompiledScript groovyScript;
        CompiledScript groovyScriptWithObj;
        ExpressRunner runner = new ExpressRunner();
        // -- other
        javax.script.Bindings bindings;
        DefaultContext<String, Object> context = new DefaultContext<String, Object>();

        @Setup
        public void myInit() {
            HelloRunning myClass = new HelloRunning();
            helloGroovy = myClass.getHelloGroovy();
            helloNativeJava = myClass.getHelloNativeJava();
            helloCompiledJava = myClass.getHelloCompiledJava();
            groovyScriptWithObj = myClass.getGroovyScriptWithObj();
            groovyScript = myClass.getGroovyScript();
            bindings = new SimpleBindings();
            bindings.put("xx", 1);
            bindings.put("yy", 2);

            context.put("xx", 1);
            context.put("yy", 2);
            System.out.println("初始化状态");
        }
    }

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
                // 导入要测试的类 -- 正则匹配
                .include(MyGyBenchmark.class.getSimpleName())
                .forks(2)
                // 打印结果时使用的单位
                .build();
        new Runner(opt).run();
    }

}

测试结果

1
2
3
4
5
6
7
8
Benchmark                                 Mode  Cnt       Score       Error   Units
MyGyBenchmark.testAlibabaQle             thrpt   16    1912.506 ±   992.858  ops/ms
MyGyBenchmark.testCompiledJava           thrpt   16  228029.546 ± 53344.121  ops/ms
MyGyBenchmark.testCompiledScript         thrpt   16     167.402 ±     9.699  ops/ms
MyGyBenchmark.testCompiledScriptWithObj  thrpt   16     167.065 ±     7.042  ops/ms
MyGyBenchmark.testGloaderByIface         thrpt   16  174037.812 ±  4290.442  ops/ms
MyGyBenchmark.testGloaderByReflect       thrpt   16   57334.248 ±   983.731  ops/ms
MyGyBenchmark.testNativeJava             thrpt   16  296561.152 ± 21977.814  ops/ms

java原生调用比groovy动态加载的直接调用快一倍
使用groovy动态加载,直接调用,比反射调用快两倍
阿里的ql吊打groovy的脚本 编译的java代码运行效率高于groovy编译的,低于原始编译的