Java 如何重用ScriptContext(或以其他方式提高性能)?

Java 如何重用ScriptContext(或以其他方式提高性能)?,java,java-8,nashorn,scriptengine,Java,Java 8,Nashorn,Scriptengine,我有一个自制的ETL解决方案。转换层在JavaScript scriptlet的配置文件中定义,由Java的Nashorn引擎解释 我遇到了性能问题。也许没有什么可以做的,但我希望有人能发现我使用Nashorn的方式有什么问题,这会有所帮助。这个过程是多线程的 我创建了一个静态脚本引擎,它只用于创建CompiledScript对象 private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("Jav

我有一个自制的ETL解决方案。转换层在JavaScript scriptlet的配置文件中定义,由Java的Nashorn引擎解释

我遇到了性能问题。也许没有什么可以做的,但我希望有人能发现我使用Nashorn的方式有什么问题,这会有所帮助。这个过程是多线程的

我创建了一个静态脚本引擎,它只用于创建CompiledScript对象

private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static CompiledScript compile(Reader reader) throws ScriptException {
    return ((Compilable) engine).compile(reader);
}
我将在每条记录上重新执行的scriptlet编译成CompiledScript对象

private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static CompiledScript compile(Reader reader) throws ScriptException {
    return ((Compilable) engine).compile(reader);
}
有两个标准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;
}
针对ScriptContext的CompiledScript的实际执行速度非常快,但是ScriptContext的初始化速度非常慢。不幸的是,至少据我所知,这必须按一组绑定进行。如果记录与筛选器匹配,那么我必须再次为同一记录重建上下文,这次使用匹配筛选器中的一些附加绑定

在创建ScriptContext的任何时候都必须重新执行这两个标准库似乎效率很低,但是在执行这些库之后但添加绑定之前,我没有找到线程安全的方法来克隆ScriptContext。如果记录与筛选器匹配,则必须重新执行两个标准库并重新附加记录中的所有绑定,这似乎也是非常低效的,但我仍然没有找到线程安全的方法来克隆记录的ScriptContext,以便在不更改原始绑定的情况下向其附加另一个绑定

根据jvisualvm的说法,我的程序的大部分时间都花在

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

如果能深入了解Nashorn,有助于提高本用例的性能,我将不胜感激。谢谢。

我成功地使用了ThreadLocal以避免串扰。这将运行1000000个测试来观察串扰,但没有发现任何串扰。这一变化意味着我创建了~4个ScriptContext对象,而不是大约8000000个

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

}
package com.foo;
导入java.util.UUID;
导入java.util.stream.stream;
导入javax.script.Bindings;
导入javax.script.compileable;
导入javax.script.CompiledScript;
导入javax.script.ScriptContext;
导入javax.script.ScriptEngine;
导入javax.script.ScriptEngineManager;
导入javax.script.ScriptException;
导入javax.script.SimpleScriptContext;
公共类酒吧{
私有静态脚本引擎;
私有静态编译脚本库;
私有静态编译脚本;
//使用本地上下文避免串扰
本地上下文的私有静态;
静止的{
试一试{
engine=new ScriptEngineManager().getEngineByName(“JavaScript”);
lib=((可编译)引擎)
.compile(“var firstChar=function(value){return value.charAt(0);};”;
脚本=((可编译)引擎).compile(“firstChar(myVar)”;
context=ThreadLocal.withInitial(()->initContext(lib));
}捕获(脚本异常){
e、 printStackTrace();
}
}
//用基库初始化ScriptContext的函数
私有静态ScriptContext initContext(CompiledScript库){
ScriptContext=新的SimpleScriptContext();
试一试{
lib.eval(上下文);
}捕获(脚本异常){
e、 printStackTrace();
}
返回上下文;
}
//用于设置变量绑定、计算脚本和捕获的函数
//lambda中的异常
专用静态字符串运行脚本(CompiledScript脚本,
ScriptContext(上下文,字符串uuid){
Bindings Bindings=context.getBindings(ScriptContext.ENGINE_作用域);
put(“myVar”,uuid);
字符串结果=null;
试一试{
结果=((字符串)script.eval(上下文));
}捕获(脚本异常){
e、 printStackTrace();
}
返回结果;
}
//生成UUID的驱动程序函数使用Nashorn获取第一个UUID
//char,使用Java获取第一个char,比较它们并打印不匹配项。
//理论上,如果存在串扰,变量绑定可能会改变
//在编译脚本的计算和java字符之间。
公共静态void main(字符串[]args){
生成(UUID::randomUUID)
.map(uuid->uuid.toString())
.限额(1000000)
.parallel()
.map(uuid->runScript(脚本,context.get(),uuid)
+uuid.charAt(0))
.filter(s->!s.substring(0,1).等于(s.substring(1,2)))
.forEach(System.out::println);
}
}
看一看