java 7 - java7 / Rhino中编译与解释JavaScript的性能

java 7 - Performance of the compiled vs. interpreted javascript in java7 / Rhino

本文关键字:解释 JavaScript 性能 编译 java7 Rhino java      更新时间:2023-09-26

我在Java7中的Rhino javascript引擎的性能有问题,很快 - 我的脚本(解析和编译文本)在Chrome中的运行速度比Java7 Rhino脚本引擎快50-100倍。

我试图找到改善情况的方法,发现 Rhino 支持脚本编译。我尝试使用我的脚本执行此操作,实际上没有看到任何改进。最后 - 我最终得到了一个虚拟的简短测试套件,我看不到编译版本和解释版本之间的性能有任何差异。请让我知道我做错了什么。

注意:一些来源提到 Rhino 引擎运行编译脚本比直接用 Java 编写的"相同"代码慢大约 1.6 个。不确定此示例中使用的"脚本编译"是否与那里应该使用的相同。

测试java类在下面,我在机器上从中获得的示例结果...

结果

    通过com.sun.script.javascript.RhinoScriptEngine@c50443运行...      时间:886毫秒,字符:38890,总和:2046720      时间:760毫秒,字符:38890,总和:2046720      时间:725毫秒,字符:38890,总和:2046720      时间:765毫秒,字符:38890,总和:2046720      时间:742毫秒,字符:38890,总和:2046720       ...3918毫秒通过com.sun.script.javascript.RhinoCompiledScript@b5c292 @ com.sun.script.javascript.RhinoScriptEngine@f92ab0运行...      时间:813毫秒,字符:38890,总和:2046720      时间:805毫秒,字符:38890,总和:2046720      时间:812毫秒,字符:38890,总和:2046720      时间:834毫秒,字符:38890,总和:2046720      时间:807毫秒,字符:38890,总和:2046720       ...4101毫秒

Anon-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%)(由 Rhino 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 in inside sun的包对我来说似乎是一个奇怪的设计 - "internal"表示这个类被假定不被开发人员使用,因此没有"认证"的方式来操纵Java 7中JS评估器的优化级别。

测试类(更新,doTest编译从外部加载 *.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();
            }
        }
    }
}

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 中的斐波那契函数大约需要 1/30 的时间作为编译的 js 实现。但也许我错过了什么。

我发现至少对于简单的程序,编译代码所花费的额外时间可能会掩盖运行它的时间。然后不要忘记,HotSpot需要一段时间才能将Java字节码编译为本机代码。

我认为,如果您使用运行时间更长的基准测试以及更复杂的代码(而不是执行大量库调用的相对简单的程序),编译版本最终会胜出,但性能仍然无法与 V8 相提并论。

Oracle正在为Java 8开发更新的EcmaScript引擎,该引擎应该更快,但还需要一段时间才能可用。

看起来我发现了问题所在 - "我的"代码中使用的编译(实际上取自互联网样本)与"编译"无关。

最后,我得到了这个链接 - https://developer.mozilla.org/en-US/docs/Rhino_JavaScript_Compiler - 犀牛的工具,用于将.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 倍)。