java7 / Rhino中编译与解释的javascript的性能

时间:2013-01-26 15:46:25

标签: javascript java-7 rhino

我在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
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%)(通过犀牛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();
            }
        }
    }
}

3 个答案:

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