Java7/Rhino中编译和解释JavaScript的性能对比

10

我在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();
            }
        }
    }
}
3个回答

6
Rhino引擎实际上能够 "在进程中" 将脚本编译成字节码,因此您无需运行工具来生成 .class 文件。您只需要设置 "优化级别",引擎将在执行脚本之前自动预编译脚本。一种覆盖优化级别的方法是使用 VM 参数 -Drhino.opt.level。将其设置为 0 到 9 中的任何值,然后运行原始测试程序,您应该会看到更好的性能。
顺便说一下,这是所提到的编译工具使用的同一优化设置。 https://developer.mozilla.org/en-US/docs/Rhino/Optimization 要完全控制程序中的优化级别和javascript版本,您可以执行以下操作。但是,您会失去 RhinoScriptEngine 类(它只是一个环境包装器而不是 javascript 引擎)提供的某些修饰。其中一个修饰是 'print' 函数,它实际上是由该包装器注入的。出于测试目的,您可以将 '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斐波那契函数所需时间只有编译后的js实现的1/30左右。

感谢您的评论。我刚有机会审查了与优化相关的内容 - 看起来确实有所不同,我已经使用新信息更新了原始问题。关于“比Java慢1.6倍”的问题 - 我记不清在哪里看到这个短语了,但我想它可能与更一般的用途有关,而不仅仅是一个特定的“斐波那契函数”。 - Xtra Coder

0

看起来我已经找到了问题所在 - 在我的代码中使用的编译(实际上是从互联网样本中获取的)与“已编译”的东西无关。

最后我找到了这个链接 - https://developer.mozilla.org/en-US/docs/Rhino_JavaScript_Compiler - 这是Rhino将.js编译成.class的工具。我使用相同的JS代码编译成.class字节码后得到了以下结果:

 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倍)。


0

我发现对于简单程序,编译代码所花费的额外时间可能会掩盖运行时间。然后别忘了,需要一点时间才能将Java字节码编译成本机代码。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接