2017-03-01 27 views
1

私は自家製のETLソリューションを持っています。変換レイヤーは、JavaScriptのスクリプトレットの設定ファイルで定義され、JavaのNashornエンジンによって解釈されます。ScriptContextを再利用するにはどうすればよいですか?

パフォーマンス上の問題が発生しています。おそらく何もできることはありませんが、誰かがNashornを助けている方法で問題を見つけることができれば幸いです。プロセスはマルチスレッドです。

私は1つの静的なScriptEngineを作成します。これは、CompiledScriptオブジェクトの作成にのみ使用されます。

private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); 

各レコードで再実行されるスクリプトレットをCompiledScriptオブジェクトにコンパイルします。

public static CompiledScript compile(Reader reader) throws ScriptException { 
    return ((Compilable) engine).compile(reader); 
} 

このメソッドを使用してコンパイルされた2つの標準JavaScriptライブラリがあります。

各レコードについて、ScriptContextが作成され、標準ライブラリが追加され、レコードの値がバインディングとして設定されます。

public static ScriptContext getContext(List<CompiledScript> libs, Map<String, ? extends Object> variables) throws ScriptException {  
    SimpleScriptContext context = new SimpleScriptContext(); 
    Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE); 

    for (CompiledScript lib : libs) { 
     lib.eval(context); 
    } 

    for (Entry<String, ? extends Object> variable : variables.entrySet()) { 
     bindings.put("$" + variable.getKey(), variable.getValue()); 
    } 
    return context; 
} 

次に、レコードのコンテキストを使用してレコードを変換し、フィルタを評価します。すべてがCompiledScriptを使用します。

public static String evalToString(CompiledScript script, ScriptContext context) throws ScriptException { 
    return script.eval(context).toString(); 
} 

ScriptContextに対するCompiledScriptの実際の実行は非常に高速ですが、ScriptContextの初期化は非常に遅いです。残念ながら、少なくとも私が理解する限り、これは束縛のセットごとに行われなければならない。レコードがフィルタと一致する場合は、同じレコードに対してもう一度コンテキストを再構築する必要があります。今回は、一致したフィルタからの追加のバインディングを使用します。

私はScriptContextを作成するたびに2つの標準ライブラリを再実行する必要がありますが、これらのライブラリが実行された後、バインディングが追加される前にScriptContextをクローンするスレッドセーフな方法が見つかりませんでした。また、2つの標準ライブラリを再実行して、フィルタと一致するレコードにすべてのバインディングを再接続する必要がありますが、レコードのScriptContextをクローンして別のバインディングを変更せずに追加するスレッドセーフな方法が見つかっていないオリジナル。 jvisualvmによると、私のプログラムの時間の大半は

jdk.internal.dynalink.support.AbstractRelinkableCallSite.initialize() (70%) 
jdk.internal.dynalink.ChainedCallSite.relinkInternal() (14%) 

に費やされ

私は、このユースケースのパフォーマンスを向上するのに役立つ可能性がありNashornに任意の洞察力をいただければ幸いです。ありがとうございました。

+1

http://stackoverflow.com/a/30159424/3920048 – Misha

答えて

0

私は、クロストークを避けるために、ThreadLocalを使用して成功することができました。これは、クロストークを監視するために1,000,000回のテストを実行し、何も見つけません。この変更は約8,000,000ではなく4つのScriptContextオブジェクトを作成することを意味します。

package com.foo; 

import java.util.UUID; 
import java.util.stream.Stream; 

import javax.script.Bindings; 
import javax.script.Compilable; 
import javax.script.CompiledScript; 
import javax.script.ScriptContext; 
import javax.script.ScriptEngine; 
import javax.script.ScriptEngineManager; 
import javax.script.ScriptException; 
import javax.script.SimpleScriptContext; 

public class Bar { 

    private static ScriptEngine engine; 
    private static CompiledScript lib; 
    private static CompiledScript script; 

    // Use ThreadLocal context to avoid cross-talk 
    private static ThreadLocal<ScriptContext> context; 

    static { 
     try { 
      engine = new ScriptEngineManager().getEngineByName("JavaScript"); 
      lib = ((Compilable) engine) 
        .compile("var firstChar = function(value) {return value.charAt(0);};"); 
      script = ((Compilable) engine).compile("firstChar(myVar)"); 
      context = ThreadLocal.withInitial(() -> initContext(lib)); 
     } catch (ScriptException e) { 
      e.printStackTrace(); 
     } 
    } 

    // A function to initialize a ScriptContext with a base library 
    private static ScriptContext initContext(CompiledScript lib) { 
     ScriptContext context = new SimpleScriptContext(); 
     try { 
      lib.eval(context); 
     } catch (ScriptException e) { 
      e.printStackTrace(); 
     } 
     return context; 
    } 

    // A function to set the variable binding, evaluate the script, and catch 
    // the exception inside a lambda 
    private static String runScript(CompiledScript script, 
      ScriptContext context, String uuid) { 
     Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE); 
     bindings.put("myVar", uuid); 
     String result = null; 
     try { 
      result = ((String) script.eval(context)); 
     } catch (ScriptException e) { 
      e.printStackTrace(); 
     } 
     return result; 
    } 

    // The driver function which generates a UUID, uses Nashorn to get the 1st 
    // char, uses Java to get the 1st char, compares them and prints mismatches. 
    // Theoretically if there was cross-talk, the variable binding might change 
    // between the evaluation of the CompiledScript and the java charAt. 
    public static void main(String[] args) { 
     Stream.generate(UUID::randomUUID) 
       .map(uuid -> uuid.toString()) 
       .limit(1000000) 
       .parallel() 
       .map(uuid -> runScript(script, context.get(), uuid) 
         + uuid.charAt(0)) 
       .filter(s -> !s.substring(0, 1).equals(s.substring(1, 2))) 
       .forEach(System.out::println); 
    } 

} 
関連する問題