2014-12-15 9 views
8

Ich implementiere etwas leistungsempfindlichen Code mit Nashorn. Ich mache es wie folgt aus:Nashorn Ineffizienz

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); 

Zur Laufzeit Nashorn besteht darauf, eine Menge notwendiger Anrufe MyScriptContext auf zu machen. Es besteht darauf, bei jedem Aufruf von eval() MyScriptContext.getBindings(). Put ("nashorn.global", anObject) aufzurufen. Dann ruft er MyScriptContext.getAttribute ("someVariable") auf (was er sollte) und ruft MyScriptContext.getAttribute ("someFunction") auf (was nicht sein sollte).

Es soll nicht die Forderung nach machen „Funktion()“, weil diese Funktion war bei der Kompilierung zu Verfügung. "someFunction()" muss zum Kompilierungszeitpunkt in kompilierte Bytecode kompiliert werden und nicht bei jedem Aufruf von eval(). eval() befindet sich in einer engen Schleife.

Wie überzeugen ich Nashorn zu weniger Anrufe MyScriptContext zu machen?

+3

"Ich implementiere Performance-sensitiven Code mit Nashorn" Warum implementieren Sie leistungsempfindliche Dinge in einer Skriptsprache? – Holger

+4

Weil ich muss. Es ist eine grundlegende Anwendungsvoraussetzung. Und ja, es macht Sinn im Zusammenhang mit dieser Anwendung. – ccleve

Antwort

32

Nashorn hat jedes global definierte Variablen (einschließlich global definierte Funktionen) im Kontext zu sehen, wie die Globals extern neu definiert werden können, und es gibt keine Möglichkeit, sie zu kennen, sind nicht neu definiert. Daher können wir eine Funktion niemals in Bytecode vorzeitig binden. Ich werde mehrere Ansätze zur Verbesserung der Leistung skizzieren.

Ihren Code in einer unmittelbar aufgerufen anonyme Funktion Ausdruck Wrap

Sie können die Leistung verbessern, indem Sie Ihr Programm in einer anonymen Funktion zu definieren, damit sie einen nicht-globalen Rahmen zu geben:

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

Darin Fall können Funktionsobjekte innerhalb der anonymen Funktion in lokalen Bytecode-Variablen gespeichert werden.

Abhängigkeit von Globals reduzieren sie, indem Sie sich als

Im allgemeinen Parameter, wenn Ihr Code Leistung empfindlich ist, minimiert die Verwendung von Globals. Wenn Sie globale Variablen verwenden müssen, können Sie sie sogar in Parameter der Funktion verschieben, damit sie dort zu lokalen Variablen werden. Z.B. Wenn Ihr Code auf Globals hängt x und y, tun:

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

Offensichtlich funktioniert dies nur für Lesezugriff auf Variablen. (Dies funktioniert natürlich mit jeder Funktion, nicht nur mit einem anonymen, sofort aufgerufenen Funktionsausdruck; es ist nur ein Konstrukt, das ich verwende, wenn ich nur vom globalen lexikalischen Kontext in einen privaten lebe).

Verwenden äußere anonyme Funktion den Code und ein anderes für Auswertungen

Eigentlich zu halten, können Sie noch besser machen. Im obigen Beispiel werten Sie immer noch den Körper der anon-Funktion aus und erstellen Funktionsobjekte. (Das ist nicht so schlimm, wohlgemerkt; sie werden nicht erneut kompiliert. Ein Funktionsobjekt ist im Wesentlichen ein Paar Zeiger: eins zum Code, eins zum lexikalischen Bereich und ist schnell zu erstellen. Code wird einmal kompiliert.) Aber in Dann können Sie Ihre Anon-Funktion des lexikalischen Gültigkeitsbereich unveränderlich machen, können Sie es nur einmal erstellen und eine Funktion von ihm zurück, die alle anderen in seinem eigenen Rahmen sehen:

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(); 
     ... 
    } 
})(); 

(an dieser Stelle, nicht wahr muss sogar CompiledScript von Java verwenden, aber ich schlage vor, dass Sie tun, wie Sie Ihre Absicht der Maschine mitteilen, dass Sie eine Darstellung wünschen, die für wiederholte Auswertung optimiert wird).

Von Java, jetzt können Sie script.eval() gefolgt von JSObject program = (JSObject)context.get("program") tun und anschließend beliebig oft mit program.call(null, x, y) aufrufen. (JSObject ist Nashorns Java-Schnittstelle für native Objekte, sowohl normale als auch Funktionen).

Alternativ können Sie auch ein anderes Skript engine.compile("program(x, y)" für den Aufruf erstellen und stellen Sie sicher, bevor eval()x und y in den Kontext um es ing.

Sie werden die meisten auf wiederholte Auswertung auf diese Weise abgeholzt. Es ist jedoch wichtig zu beachten, dass alle Aufrufe den lexikalischen Umfang des äußersten anonymen Aufrufs gemeinsam haben. Auf diese Weise erhalten Sie die gleichen Funktionsobjekte, ohne sie jemals neu erstellen zu müssen, aber auch, wenn Sie dort einen änderbaren Status haben (einige var s im Funktionsumfang), werden sie ebenfalls geteilt.

+2

Dies ist eine hervorragende Antwort. Du solltest es irgendwo zu einem Blogpost machen. – ccleve

+0

@ccleve, Sie kommentieren die Antwort und ich stimme Ihnen zu, dass es eine nette Antwort ist. Aber noch wichtiger: Haben Sie seinen Vorschlag umgesetzt und wenn ja, werden sie zu Leistungssteigerungen? Bitte kommentieren oder aktualisieren Sie Ihre Frage zu Verbesserungen. Vielen Dank! – Jeach

+0

Gibt es einen Benchmark für Nashorn? Wir versuchen ein js-Paket zu erstellen, das von Webpack erstellt wurde und Nashorn scheint um 50% langsamer zu sein als Rhino (läuft im interpretierten Modus, weil das Paket zu groß ist). Ist das real oder fehlt etwas? – Katona

Verwandte Themen