我在Java7的Rhino javascript引擎中遇到了性能问题,我的脚本(用于解析和编译文本)在Chrome中运行的速度比Java7 Rhino脚本引擎中的速度快50-100倍。
我尝试找到改善情况的方法,并发现Rhino支持脚本编译。我尝试使用我的脚本进行编译,实际上并没有看到任何改进。最后,我以一个虚拟的简短测试套件结束,其中我在编译版本和解释版本之间看不到性能差异。请告诉我我做错了什么。
注意:一些来源提到Rhino引擎运行编译后的脚本大约比直接在Java中编写的“相同”代码慢1.6倍。不确定此示例中使用的“脚本编译”是否与该源所述的编译方式相同。
以下是测试Java类和我从其中获得的样本结果...
结果
Running via com.sun.script.javascript.RhinoScriptEngine@c50443 ... time: 886ms, chars: 38890, sum: 2046720 time: 760ms, chars: 38890, sum: 2046720 time: 725ms, chars: 38890, sum: 2046720 time: 765ms, chars: 38890, sum: 2046720 time: 742ms, chars: 38890, sum: 2046720 ... 3918ms
Running via com.sun.script.javascript.RhinoCompiledScript@b5c292 @ com.sun.script.javascript.RhinoScriptEngine@f92ab0 ... time: 813ms, chars: 38890, sum: 2046720 time: 805ms, chars: 38890, sum: 2046720 time: 812ms, chars: 38890, sum: 2046720 time: 834ms, chars: 38890, sum: 2046720 time: 807ms, chars: 38890, sum: 2046720 ... 4101ms
更新匿名微评论后:
在测试类中将JavaScript eval()和compile()的调用包装成...
import sun.org.mozilla.javascript.internal.Context;
try {
Context cx = Context.enter();
cx.setOptimizationLevel(9);
cx.setLanguageVersion(170);
...
}
finally {
Context.exit();
}
测试结果发生了显著变化-从平均1.8秒(在新版本的测试类中)到约150毫秒。然而,通过(CompiledScript = Compilable.compile()). eval(Bindings) -> Bindings.get("doTest")
加载的ScriptEngine提取的doTest()函数的实例仍然表明它是sun.org.mozilla.javascript.internal.InterpretedFunction
,其性能略低于从预编译的字节码(由Rhino 1.7r4)加载的JS版本(约为10%)-因此我仍然不确定幕后实际发生了什么。
1800ms - ScriptEngine.eval(), Optimization Level = default(-1?)
1758ms - CompiledScript, Optimization Level = default(-1?)
165ms - ScriptEngine.eval(), Optimization Level = 9
132ms - CompiledScript, Optimization Level = 9
116ms - compiled by Rhino 1.7r4 into bytecode class
PS:sun.org.mozilla.javascript.internal.Context 在sun的内部包中看起来对我来说是一个奇怪的设计 - 'internal'表示这个类被假定不会被开发人员使用,因此在Java 7中没有'认证'的方法来操纵JS评估器的优化水平。
测试类(已更新,doTestCompiled从外部*.class加载)
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
import sun.org.mozilla.javascript.internal.Context;
import sun.org.mozilla.javascript.internal.Scriptable;
import sun.org.mozilla.javascript.internal.Function;
public class RhinoPerfTest4 {
final static ScriptEngineManager scm = new ScriptEngineManager();
final static String TEST_SCRIPT1 =
"function doTest() {\n"
+ " var scale = 5000, i, a = [], str, l, sum = 0,\n"
+ " start = (new Date()).getTime(), end;\n"
+ " for( i = 0; i < scale; i++ )\n"
+ " a.push(\"\" + i);\n"
+ " str = a.join(\"\");\n"
+ " l = str.length;\n"
+ " for( i = 0; i < l; i++ ) {\n"
+ " var c = str.charCodeAt(i);\n"
+ " if( c > 0)\n"
+ " sum += c;\n"
+ " }\n"
+ " end = (new Date()).getTime();\n"
+ "\n"
+ " // print(\" time: \" + (end - start) "
+ " + \"ms, chars: \" + l "
+ " + \", sum: \" + sum + \"\\n\");\n"
+ "}\n";
final static String TEST_SCRIPT2 =
"function doTest() {\n"
+ " var a = [], i;\n"
+ " for( i = 0; i < 500; i++ ) a.push(1);\n"
+ "}\n";
static class TestSet {
public int nCycles;
public String script;
public TestSet(int nCycles, String script) {
this.nCycles = nCycles;
this.script = script;
}
}
static TestSet set1 = new TestSet(5, TEST_SCRIPT1);
static TestSet set2 = new TestSet(500, TEST_SCRIPT2);
public static void main(String[] args) throws Exception {
ScriptEngine se;
int i;
long ts, te;
TestSet set = set1;
Object noArgs[] = new Object[]{};
try {
org.mozilla.javascript.Context mctx = org.mozilla.javascript.Context.enter();
se = scm.getEngineByExtension("js");
doTestCompiled doTestPreCompiled = new doTestCompiled();
org.mozilla.javascript.Scriptable scope = mctx.initStandardObjects();
doTestPreCompiled.call(mctx, scope, scope, null);
org.mozilla.javascript.Function doTest =
(org.mozilla.javascript.Function)scope.get("doTest", null);
for( int nHotSpot = 0; nHotSpot < 5; nHotSpot++ ) {
if( nHotSpot > 0 )
Thread.sleep(500);
ts = System.currentTimeMillis();
for( i = 0; i < set.nCycles; i++ ) {
doTest.call(mctx, scope, null, null);
}
te = System.currentTimeMillis();
System.out.println(" " + nHotSpot + ": " + (te - ts) + "ms");
}
}
finally {
org.mozilla.javascript.Context.exit();
}
for( int nOpt = 0; nOpt < 2; nOpt++ ) {
if( nOpt > 0 )
Thread.sleep(500);
Context cx = null;
try {
System.out.println("Cycle: " + nOpt);
cx = Context.enter();
if( nOpt > 0 ) {
System.out.println("OptLevel: " + 9);
cx.setOptimizationLevel(9);
cx.setLanguageVersion(170);
}
se = scm.getEngineByExtension("js");
se.eval(set.script);
System.out.println("\nRunning via " + se + " ... ");
Invocable invocable = (Invocable) se;
for( int nHotSpot = 0; nHotSpot < 5; nHotSpot++ ) {
if( nHotSpot > 0 )
Thread.sleep(500);
ts = System.currentTimeMillis();
for( i = 0; i < set.nCycles; i++ ) {
invocable.invokeFunction("doTest", noArgs);
}
te = System.currentTimeMillis();
System.out.println(" " + nHotSpot + ": " + (te - ts) + "ms");
}
se = scm.getEngineByExtension("js");
Compilable cse = (Compilable) se;
CompiledScript cs = cse.compile(set.script/* + "(doTest())"*/);
Scriptable scope = cx.initStandardObjects();
ScriptContext scriptContext = new SimpleScriptContext();
Bindings vars = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
cs.eval(vars);
Object odoTest = scriptContext.getAttribute("doTest");
Function doTest = (Function) vars.get("doTest");
System.out.println("\nRunning via " + cs + " @ " + se + " ... ");
for( int nHotSpot = 0; nHotSpot < 5; nHotSpot++ ) {
if( nHotSpot > 0 )
Thread.sleep(500);
ts = System.currentTimeMillis();
for( i = 0; i < set.nCycles; i++ ) {
doTest.call(cx, scope, null, noArgs);
}
te = System.currentTimeMillis();
System.out.println(" " + nHotSpot + ": " + (te - ts) + "ms");
}
}
finally {
if( cx != null )
Context.exit();
}
}
}
}