Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nashorn inefficiency

Tags:

java-8

nashorn

I'm implementing some performance-sensitive code using Nashorn. I do it like this:

private ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(new String[] { "--no-java" });

String someExpression = "someFunction() + someVariable";

// this compiled script gets cached, caching code omitted
CompiledScript script = ((Compilable)engine).compile(expr);

MyScriptContext context = new MyScriptContext();

Object output = script.eval(context);

At runtime, Nashorn insists on making a lot of necessary calls to MyScriptContext. It insists on calling MyScriptContext.getBindings().put("nashorn.global", anObject) on every call to eval(). It then makes a call to MyScriptContext.getAttribute("someVariable") (which it should) and a call to MyScriptContext.getAttribute("someFunction") (which it should not).

It should not make the call for "someFunction()" because that function was available to it at compile time. "someFunction()" needs to get compiled to bytecode and bound in at compile time, not on every call to eval(). eval() is in a tight loop.

How do I persuade Nashorn to make fewer calls to MyScriptContext?

like image 411
ccleve Avatar asked Dec 15 '14 22:12

ccleve


1 Answers

Nashorn has to look up every globally defined variable (including globally defined functions) in the context as the globals can be redefined externally and there's no way to know they aren't redefined. Therefore, we can't ever early-bind a function in bytecode. I'll outline several approaches for improving the performance.

Wrap your code in an immediately invoked anonymous function expression

You could improve the performance by defining your program inside an anonymous function, thus giving it a non-global scope:

(function() {
    // put your original code here, like this:
    // ...
    function someFunction() { ... }
    ...
    someFunction();
    ...
})();

In that case, function objects inside the anonymous function can end up being stored in bytecode local variables.

Reduce dependence on globals by passing them in as parameters

In general, if your code is performance sensitive, minimize its use of globals. If you need to use globals , you can even move them into parameters of the function so they become local variables there. E.g. if your code depends on globals x and y, do:

(function(x, y) {
    // put your original code here, like this:
    // ...
    function someFunction() { ... }
    ...
    someFunction();
    ...
})(x, y);

Obviously, this only works for read-access to variables. (This works with any function, of course, not just an anonymous immediately invoked function expression; it's just a construct I use when all I need is move from global lexical context into a private one).

Use outer anonymous function to hold the code and another one for evaluations

Actually, you can do even better. In the above example, you'll still evaluate the body of the anon function, and it will be creating function objects. (That's not so bad, mind you; they won't get compiled again. A function object is essentially a pair of pointers: one to code, one to the lexical scope and is fast to create. Code is compiled once.) But in case you can make your anon function's lexical scope immutable, you can just create it once and return a function from it that will see all the others in its own scope:

var program = (function() {
   // put all your function declarations and other constants here
   ...
   function someFunction() { ... }
   ...
   return new function(x, y) {
       // put your original code, minus the function declarations etc. here
       ...
       someFunction();
       ...
   }
})();

(At this point, you don't even have to use CompiledScript from Java, but I suggest you do as you communicate your intent towards the engine that you want a representation optimized for repeated evaluation).

From Java, now you can do script.eval() followed by JSObject program = (JSObject)context.get("program") and subsequently invoke it any number of times with program.call(null, x, y). (JSObject is Nashorn's Java-facing interface for native objects, both ordinary and functions).

Alternatively, you can create a different script using engine.compile("program(x, y)" for invocation and make sure to put x and y into the context before eval()ing it.

You'll cut down the most on repeated evaluation this way. What's important to notice though is that all invocations will share the lexical scope of the outermost anonymous invocation. That's how you get the same function objects without the need to ever recreate them, but also beware that if you have any mutable state in there (some vars in the function scope) they'll be shared too.

like image 138
Attila Szegedi Avatar answered Sep 25 '22 19:09

Attila Szegedi