我在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 ... 4101msAnon-Micro发表评论后
更新:
将测试类中的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
,并且其性能比从预编译的字节码加载的JS版本稍差(约10%)(通过犀牛1.7r4) - 所以我仍然不确定幕后发生了什么。
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();
}
}
}
}
答案 0 :(得分:6)
Rhino引擎实际上能够将脚本编译为进程内的字节码,因此您无需先运行该工具来生成.class文件。您只需设置“优化级别”,引擎将在执行前自动预编译脚本。覆盖优化级别的一种方法是使用VM参数-Drhino.opt.level。将此设置为0到9之间的任何值并运行原始测试程序,您应该会看到更好的性能。
顺便提一下,这与您提到的编译工具使用的优化设置相同。 https://developer.mozilla.org/en-US/docs/Rhino/Optimization
要在程序中完全控制优化级别和javascript版本,您可以执行以下操作。但是你丢失了RhinoScriptEngine类(它只是一个环境包装器而不是javascript引擎)提供的一些修饰。一种这样的修剪是'打印'功能,它实际上由所述包装物注入。出于测试目的,您只需将'print'替换为'java.lang.System.out.print'。
int optimisationLevel = 3;
int languageVersion = Context.VERSION_1_7;
try {
Context cx = Context.enter();
cx.setOptimizationLevel(optimisationLevel);
cx.setLanguageVersion(languageVersion);
ImporterTopLevel scope = new ImporterTopLevel(cx);
cx.evaluateString(scope, TEST_SCRIPT1, "doTest", 1, null);
for (int i = 0; i < 10; i++)
cx.evaluateString(scope, "doTest();", "", 1, null);
} finally {
Context.exit();
}
你提到了以下内容:
注意:有些消息来源提到Rhino引擎运行编译脚本比直接用Java编写的“相同”代码慢大约1.6。不确定此示例中使用的“脚本编译”是否与假定的相同。
我对报告此内容的来源感兴趣,java中的我的fibonacci函数大约需要1/30的时间作为编译的js实现。但也许我错过了一些东西。
答案 1 :(得分:0)
我发现至少对于简单的程序来说,编译代码所花费的额外时间可能会使运行它的时间蒙上阴影。然后不要忘记,在HotSpot将Java字节码编译为本机代码之前需要一段时间。
我认为如果你使用更长时间运行的基准测试和更复杂的代码(相对于执行大量库调用的相对简单的程序),编译后的版本最终会胜出,但性能仍然不具有可比性到V8。
Oracle正在为Java 8开发一个更新的EcmaScript引擎,它应该更快,但在它可用之前还需要一段时间。
答案 2 :(得分:0)
看起来我发现了什么错误 - “我的”代码中使用的编译(实际上是从互联网样本中获取)与“编译”无关。
最后我得到了这个链接 - https://developer.mozilla.org/en-US/docs/Rhino_JavaScript_Compiler - Rhino将.js编译成.class的工具。我使用从.class的字节码编译的相同JS代码得到以下结果:
time: 202ms, chars: 38890, sum: 2046720
time: 92ms, chars: 38890, sum: 2046720
time: 73ms, chars: 38890, sum: 2046720
...
time: 71ms, chars: 38890, sum: 2046720
time: 66ms, chars: 38890, sum: 2046720
time: 64ms, chars: 38890, sum: 2046720
... 1143ms (per 15 iterations)
--- sleep 5 secs ---
time: 64ms, chars: 38890, sum: 2046720
time: 52ms, chars: 38890, sum: 2046720
time: 64ms, chars: 38890, sum: 2046720
...
time: 62ms, chars: 38890, sum: 2046720
time: 67ms, chars: 38890, sum: 2046720
time: 67ms, chars: 38890, sum: 2046720
... 962ms
--- sleep 5 secs ---
time: 66ms, chars: 38890, sum: 2046720
time: 56ms, chars: 38890, sum: 2046720
time: 59ms, chars: 38890, sum: 2046720
...
time: 69ms, chars: 38890, sum: 2046720
time: 67ms, chars: 38890, sum: 2046720
time: 59ms, chars: 38890, sum: 2046720
... 966ms
(这大约快10倍)
这是Chrome的结果:
time: 5ms, chars: 38890, sum: 2046720
time: 3ms, chars: 38890, sum: 2046720
time: 1ms, chars: 38890, sum: 2046720
time: 1ms, chars: 38890, sum: 2046720
time: 1ms, chars: 38890, sum: 2046720
time: 5ms, chars: 38890, sum: 2046720
time: 0ms, chars: 38890, sum: 2046720
time: 1ms, chars: 38890, sum: 2046720
time: 1ms, chars: 38890, sum: 2046720
time: 1ms, chars: 38890, sum: 2046720
(平均为3-4毫秒,比编译的Java / Rhino快15倍,比解释的Java / Rhino快〜200倍)。