10

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

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

注意:一些消息来源提到 Rhino 引擎运行编译脚本比直接用 Java 编写的“相同”代码慢大约 1.6。不确定此示例中使用的“脚本编译”是否与此处假定的相同。

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

结果

     通过 com.sun.script.javascript.RhinoScriptEngine@c50443 运行 ...
      时间:886ms,字符:38890,总和:2046720
      时间:760ms,字符:38890,总和:2046720
      时间:725ms,字符:38890,总和:2046720
      时间:765ms,字符:38890,总和:2046720
      时间:742ms,字符:38890,总和:2046720
       ... 3918 毫秒


     通过 com.sun.script.javascript.RhinoCompiledScript@b5c292 @ com.sun.script.javascript.RhinoScriptEngine@f92ab0 运行 ...
      时间:813ms,字符:38890,总和:2046720
      时间:805ms,字符:38890,总和:2046720
      时间:812ms,字符:38890,总和:2046720
      时间:834ms,字符:38890,总和:2046720
      时间:807ms,字符: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 毫秒。然而,从通过加载的 ScriptEngine 提取的 doTest() 函数的实例(CompiledScript = Compilable.compile()).eval(Bindings) -> Bindings.get("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 包中的 sun.org.mozilla.javascript.internal.Context 对我来说似乎是一个奇怪的设计 - '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();
            }
        }
    }
}
4

3 回答 3

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

于 2013-03-10T21:13:01.780 回答
0

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

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

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

于 2013-01-26T19:52:42.267 回答
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 倍)。

于 2013-01-26T21:32:55.717 回答