我有一个自制的 ETL 解决方案。转换层在 JavaScript 脚本的配置文件中定义,由 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();
}
CompiledScripts 针对 ScriptContext 的实际执行速度非常快,但是 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);
}
}