在JVM中编译脚本提升性能

作者:聂勇 欢迎转载,请保留作者信息并说明文章来源!

JDK6增加了一个重要的特性,实现JSR 223规范支持脚本语言,使得Java可以通过脚本引擎执行JavaScript,Python等脚本语言代码。JDK6包含了一个基于Mozilla Rhino的 脚本语言引擎,支持JavaScript。其他的脚本语言可以按JSR 223规范实现脚本引擎,JDK中有一个ScriptEngineManager类,它可以通过jar服务发现(Service Discovery)机制寻找合适的脚本引擎类(ScriptEngine)。

脚本语言给人第一印象,它是逐行解释执行的,但JSR 223规范定义了脚本两种不同的运行方式:

  • 解释运行。
  • 先编译,后运行。

是否支持编译功能,由具体的脚本引擎决定。JDK6自带的JavaScript引擎同时支持解释运行和编译运行两种方式,下面就对比这两种方式下脚本运行的性能。

代码

完整的源代码
1、解释运行。关键代码如下:

1
2
3
4
5
6
7
8
9
10
public void parse(String script, Map<String, Object> vars) throws ScriptException {
ScriptEngine scriptEngine = getScriptEngine("javascript");
long startTime = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
Bindings binds = createBinding(vars, scriptEngine);
scriptEngine.eval(script, binds);
}
long usedTime = System.currentTimeMillis() - startTime;
System.out.println( String.format("每次都解释脚本执行%d次消耗%d毫秒", COUNT, usedTime) );
}

2、先编译,再运行。关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void compile(String script, Map<String, Object> vars) throws ScriptException {
ScriptEngine scriptEngine = getScriptEngine("javascript");
Compilable compileEngine = (Compilable) scriptEngine;
CompiledScript compileScript = compileEngine.compile(script);
long startTime = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
Bindings binds = createBinding(vars, scriptEngine);
compileScript.eval(binds);
}
long usedTime = System.currentTimeMillis() - startTime;
System.out.println( String.format("编译脚本后执行%d次消耗%d毫秒", COUNT, usedTime) );
}

性能对比

测试环境,运行在VirtualBox中的虚拟机:
操作系统:Redhat 5.7 64bit
CPU:Intel(R) Core(TM) i3 CPU M380@2.53GHz * 2
内存:2.5GB

跑了四次测试,结果如下:

运行次数 解释运行(单位:毫秒) 先编译再运行(单位:毫秒)
第一次 100000 16898 15129
第二次 100000 15704 14291
第三次 100000 17724 14938
第四次 100000 16987 14828

注:在高并发的场景中,两种方式的性能差距比上述结果要更大。