10

Java7でのRhinojavascriptエンジンのパフォーマンスに問題があります。まもなく、私のスクリプト(テキストを解析およびコンパイルする)は、Java7 Rhinoスクリプトエンジンでのスクリプトよりも約50〜100倍速くChromeで実行されます。

私は状況を改善する方法を見つけようとしていて、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
       ...3918ms


     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
       ...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ミリ秒に。ただし、経由でロードされたScriptEngineから抽出されたdoTest()関数のインスタンスは、それがそうであると(CompiledScript = Compilable.compile()).eval(Bindings) -> Bindings.get("doTest")言っておりsun.org.mozilla.javascript.internal.InterpretedFunction、そのパフォーマンスは、コンパイル済みのバイトコードからロードされたJSのバージョン(Rhino 1.7r4による)よりもわずかに劣っています(約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」は、このクラスが開発者によって使用されないと想定されているため、「認定された」方法がないことを示しますJava7で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 ファイルを生成する必要はありません。「最適化レベル」を設定するだけで、エンジンはスクリプトを実行する前に自動的にプリコンパイルします。最適化レベルをオーバーライドする 1 つの方法は、VM 引数 -Drhino.opt.level を使用することです。これを 0 から 9 の間の任意の値に設定し、元のテスト プログラムを実行すると、パフォーマンスが向上するはずです。

ちなみに、これはあなたが言及したコンパイルツールで使用されるのと同じ最適化設定です。https://developer.mozilla.org/en-US/docs/Rhino/Optimization

プログラム内の最適化レベルと JavaScript バージョンを完全に制御するには、次のようにします。ただし、RhinoScriptEngine クラス (JavaScript エンジンではなく単なる環境ラッパー) が提供するトリミングの一部が失われます。そのようなトリミングの 1 つが、上記のラッパーによって実際に挿入される「印刷」機能です。テスト目的で、'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の時間を要します. しかし、おそらく私は何かを見逃しています。

于 2013-03-10T21:13:01.780 に答える
0

少なくとも単純なプログラムの場合、コードのコンパイルに費やされる余分な時間が、それを実行する時間に影を落とす可能性があることがわかりました。次に、HotSpot が Java バイトコードをネイティブ コードにコンパイルするまでに少し時間がかかることを忘れないでください。

(多くのライブラリ呼び出しを行う比較的単純なプログラムとは対照的に)より複雑なコードで実行時間の長いベンチマークを使用した場合、コンパイルされたバージョンが最終的に勝つと思いますが、パフォーマンスは依然として V8 に匹敵しません。

Oracle はより高速な Java 8 用の新しい EcmaScript エンジンに取り組んでいますが、それが利用可能になるまでにはしばらく時間がかかるでしょう。

于 2013-01-26T19:52:42.267 に答える