Reputation: 2469
I'm having some performance issues because executing Javascript code in Rhino is slow.
I wrote the following tests to see how performance changed with different ways of executing scripts:
public class SimpleScriptsPerformanceTest {
private static int TIMES = 10000;
// no caching scope; without optimizations
@Test
public void testRhino1() {
Context ctx = Context.enter();
try {
ctx.setLanguageVersion(170);
ScriptableObject scriptScope = ctx.initStandardObjects();
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (rhino1): " + (endTime-startTime) + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
}
// no caching scope; with optimizations
@Test
public void testRhino2() {
Context ctx = Context.enter();
try {
ctx.setOptimizationLevel(9);
ctx.setLanguageVersion(170);
ScriptableObject scriptScope = ctx.initStandardObjects();
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (rhino2): " + (endTime-startTime) + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
}
// caching scope; with optimizations for main function and for calling code
@Test
public void testRhino3() {
Context ctx = Context.enter();
try {
ctx.setOptimizationLevel(9);
ctx.setLanguageVersion(170);
ScriptableObject scriptScope = ctx.initStandardObjects();
ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
ctx.setOptimizationLevel(9);
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
ctx.evaluateString(scriptScope, getScript2(), "script.js", 1, null);
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (rhino3): " + (endTime-startTime) + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
}
// caching scope; with optimizations for main function; no optimizations for calling code
@Test
public void testRhino4() {
Context ctx = Context.enter();
try {
ctx.setOptimizationLevel(9);
ctx.setLanguageVersion(170);
ScriptableObject scriptScope = ctx.initStandardObjects();
ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
ctx.setOptimizationLevel(-1);
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
ctx.evaluateString(scriptScope, getScript2(), "script.js", 1, null);
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (rhino4): " + (endTime-startTime) + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
}
// caching scope; without optimizations; different contexts
@Test
public void testRhino5() {
ScriptableObject scriptScope = null;
Context ctx = Context.enter();
try {
ctx.setOptimizationLevel(9);
ctx.setLanguageVersion(170);
scriptScope = ctx.initStandardObjects();
ctx.evaluateString(scriptScope, getScript1(), "script.js", 1, null);
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
ctx = Context.enter();
try {
ctx.setOptimizationLevel(-1);
ctx.setLanguageVersion(170);
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
ctx.evaluateString(scriptScope, getScript2(), "script.js", 1, null);
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (rhino5): " + (endTime-startTime) + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
Context.exit();
}
}
// script engine; eval
@Test
public void testScriptEngine1() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
engine.eval(getScript1());
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (scriptEngine1): " + (endTime-startTime) + "ms");
}
// script engine; compiled script
@Test
public void testScriptEngine2() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
Compilable compilingEngine = (Compilable)engine;
CompiledScript script = compilingEngine.compile(getScript1());
long startTime = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
script.eval();
}
long endTime = System.currentTimeMillis();
System.out.println("Total execution time (scriptEngine2): " + (endTime-startTime) + "ms");
}
private String getScript1() {
StringBuilder sb = new StringBuilder();
sb.append("var foo = function() {").append("\n");
sb.append(" var a = 5;").append("\n");
sb.append(" var b = 'test';").append("\n");
sb.append(" var c = 93.2;").append("\n");
sb.append(" var d = [];").append("\n");
sb.append(" for (var i = 0; i < 5; i++) {").append("\n");
sb.append(" if (i == 2) d.push('pepe');").append("\n");
sb.append(" else d.push('juan');").append("\n");
sb.append(" }").append("\n");
sb.append(" return res = a + b + c + d.join(',');").append("\n");
sb.append("}").append("\n");
sb.append("foo();").append("\n");
return sb.toString();
}
private String getScript2() {
StringBuilder sb = new StringBuilder();
sb.append("foo();").append("\n");
return sb.toString();
}
}
After running the script several times, I got the following average times:
Total execution time (rhino1): 8120 ms
Total execution time (rhino2): 7946 ms
Total execution time (rhino3): 4350 ms
Total execution time (rhino4): 257 ms
Total execution time (rhino5): 188 ms
Total execution time (scriptEngine1): 1547 ms
Total execution time (scriptEngine2): 1090 ms
So rhino5 seems to be the most efficient of the above, where put the function 'foo' in the scope just once and then, in a different context, I call that function within the same scope with no optimizations at all.
From those results these are the conclusions I get:
So here goes my question: is there a way to run Rhino scripts in a more efficient way?
I have scripts that won't change often (but do change and need to update them without stopping the application) so I can compile them and reuse them. However I'm not sure what I'm doing (caching and reusing the scope) is the most efficient way. I've seen that some people recommend to compile Javascript to Java bytecode, but not sure how that can be done.
Notes:
Upvotes: 3
Views: 2209
Reputation: 2879
The Rhino engine implements the Compilable interface. I asume that will be faster than having to evaulate the script string all the time.
Compilable compilingEngine = (Compilable) cEngine;
CompiledScript compiledScript = compilingEngine.compile(script);
Upvotes: 1