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

标签 java java-8 nashorn scriptengine

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

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

我创建了一个静态 ScriptEngine,它仅用于创建 CompiledScript 对象。

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

我将在每条记录上重新执行的 scriptlet 编译成 CompiledScript 对象。

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

有两个标准的 JavaScript 库也是使用这种方法编译的。

对于每条记录,都会创建一个 ScriptContext,添加标准库,并将记录的值设置为绑定(bind)。

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

记录的上下文然后用于转换记录和评估过滤器,所有这些都使用 CompiledScripts。

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

针对 ScriptContext 的 CompiledScripts 的实际执行速度非常快,但是 ScriptContexts 的初始化非常慢。不幸的是,至少据我所知,这必须在每组绑定(bind)中完成。如果记录与过滤器匹配,那么我必须为同一条记录再次重建上下文,这次使用来自匹配过滤器的一些额外绑定(bind)。

每当我创建 ScriptContext 时都必须重新执行这两个标准库似乎效率很低,但是我发现没有线程安全的方法可以在执行这些库之后但在添加绑定(bind)之前克隆 ScriptContext。如果它与过滤器匹配,则必须重新执行两个标准库并重新附加记录中的所有绑定(bind)似乎也非常低效,但我再次发现没有线程安全的方法来克隆记录的 ScriptContext 以在不改变的情况下向其附加另一个绑定(bind)原来的。

根据 jvisualvm,我程序的大部分时间花在了

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

如果对 Nashorn 的任何见解都有助于提高此用例的性能,我将不胜感激。谢谢。

最佳答案

我能够成功地使用 ThreadLocal 来避免串扰。这会运行 1,000,000 次测试以观察串扰,但没有发现任何问题。此更改意味着我创建了约 4 个 ScriptContext 对象,而不是大约 8,000,000 个。

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

}

关于java - 我如何重用 ScriptContext(或以其他方式提高性能)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42543730/

相关文章:

java - H2DB和Java,大约>两个小时的差异

JAVA 正则表达式 : Making custom regex for String. split()

java - 使用 Java8 列表到 map

java - 在 Mac 上使用 JRE 打开 jar 文件

java - 禁用 JButtons 从键盘读取事件

java - 从 lambda 表达式引用的局部变量必须是最终的或实际上是最终的

java - Spring Framework 3.2.5 - 对 POST API 输出的响应正文进行解码的问题

java - Nashorn 类型错误 : Cannot call undefined in <eval>

Java8 Javascript Nashorn 异常 : no current Global instance for nashorn

javascript - 在via nashorn中实例化类