Performance of the compiled vs. interpreted javascript in java7 / Rhino
Asked Answered
W

3

11

I have a problem with performance of Rhino javascript engine in Java7, shortly - my script (that parses and compiles texts) runs in Chrome around 50-100 times quicker than the same in Java7 Rhino script engine.

I was trying to find the way to improve situation and have found that Rhino supports compilation of scripts. I tried doing it with my scripts and actually did not see any improvement. Finally - i ended up with a dummy short test suite where i do not see any difference in performance between compiled and interpreted versions. Please let me know what I'd doing wrong.

Note: some sources mention that Rhino engine runs compiled script roughly 1.6 slower than the "same" code written directly in Java. Not sure if "compilation of script" used in this sample the is same one which is supposed there.

Test java class is below and sample result I'm getting from it on my machine ...

Results

     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

Update after comment from Anon-Micro:

After wrapping invocation of the JavaScript eval() and compile() in test class into ...

import sun.org.mozilla.javascript.internal.Context;
try {
    Context cx = Context.enter();

    cx.setOptimizationLevel(9);
    cx.setLanguageVersion(170);

    ...
}
finally {
    Context.exit();
}

result changed signigicantly - from average 1.8 (in new version of test class) sec to ~150msec. However instance of the doTest() function extracted from ScriptEngine loaded via (CompiledScript = Compilable.compile()).eval(Bindings) -> Bindings.get("doTest") still says it is sun.org.mozilla.javascript.internal.InterpretedFunction and its performance is slightly worse (around 10%) than version of JS loaded from pre-compiled bytecode (by Rhino 1.7r4) - so i'm still not sure what is actually happening behind the scene.

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 within internal sun's package looks to be a weird design for me - 'internal' denotes this class is assumed not to be used by developers and therefor there is not 'certified' way to manipulate optimization level of JS evaluator in Java 7.

Test class (updated, doTestCompiled is loaded from external *.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();
            }
        }
    }
}
Wreathe answered 26/1, 2013 at 15:46 Comment(0)
C
6

The Rhino engine is actually capable of compiling scripts into bytecode 'in-process', so you don't need to run the tool to generate .class files first. You only need to set 'optimisation level' and the engine will automatically pre-compile the script before executing it. One way to override the optimisation level is with the VM argument -Drhino.opt.level. Set this to anything between 0 and 9 and run your original test program and you should see better performance.

This is the same optimisation setting used by the compiling tool you mentioned, by the way. https://developer.mozilla.org/en-US/docs/Rhino/Optimization

For total control of optimisation level and javascript version within your program, you can do the following. However you lose some of the trimmings that RhinoScriptEngine class (which is just a environment wrapper and not the javascript engine) provides. One such trimming is the 'print' function which is actually injected by said wrapper. For test purposes you can just replace 'print' with '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();
    }

You mentioned the following:

Note: some sources mention that Rhino engine runs compiled script roughly 1.6 slower than the "same" code written directly in Java. Not sure if "compilation of script" used in this sample the is same one which is supposed there.

I'd be interested in the source that reported this, My fibonacci function in java takes about 1/30 the time as the compiled js implementation. But perhaps I'm missing something.

Coomb answered 10/3, 2013 at 21:13 Comment(1)
Thanks for your comment. Just got chance to review the Optimization related staff - it looks like it really does the difference, i've updated original question with new information. ... Re: "1.6 slower than same in java" - i can't remember where i have seen this phrase, but i guess it relates to more general purposes usages than just a specific 'fibonacci function'.Wreathe
L
0

I've found that for simple programs at least, the extra time spent compiling your code can overshadow the time running it. Then don't forget, it takes a little while before HotSpot compiles the Java bytecode to native code.

I think if you used a longer-running benchmark with more complex code (as opposed to a relatively simple program that's doing a lot of library calls) the compiled version would eventually win out, but the performance still won't be comparable to V8.

Oracle is working on a newer EcmaScript engine for Java 8 that should be faster, but it's going to be awhile before that's available.

Lumpkin answered 26/1, 2013 at 19:52 Comment(0)
W
0

It looks like i've found what is wrong - compilation used in 'my' code (actually taken from internet samples) has nothing to do with 'compiled'.

Finally i've got to this link - https://developer.mozilla.org/en-US/docs/Rhino_JavaScript_Compiler - the Rhino's tool to compile .js into .class. I've got following result with the same JS code running compiled from .class's bytecode:

 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

(this is roughly 10 time quicker)

and this is the result from 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

(average is 3-4 msec, ~15 quicker than compiled Java/Rhino, and ~200 times quicker than interpreted Java/Rhino).

Wreathe answered 26/1, 2013 at 21:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.