我在Nashorn表现不佳,我无法真正解释原因。我将详细说明我的设置是什么以及如何调试它。
硬件:相当不错的服务器硬件('13时代-12核心Xeon,2.1GHz)。 64GB DDR3 RAM。
软件:Oracle JDK8(最新的64位)(40GB RAM已预先分配给JVM)。
我的实现是:多个Nashorn ScriptEngine实例,每个实例都有一个预编译的“ utility.js”,其中提供了一些用户定义的脚本可以利用的辅助功能。
我有一个ScriptEngine对象池,它们已经准备好与已经针对它们编译的Utility.js和一个线程分配器一起使用,该线程分配器会将线程加速到设定的限制。每个线程将获取一个预分配的ScriptEngine,并使用新的上下文将用户JS评估给它,并执行它/将结果存储在某个地方,然后再将ScriptEngine返回到池中。这一切都很好,如果我的用户脚本非常简单(单个功能),它的运行速度将非常快。
但是,大多数用户脚本相当大,其形式为:
function myFunc() {
myFunc1();
myFunc2();
... (you get the picture, they define and call a lot of functions!)
myFunc100();
}
function myFunc1() {
// do something simple here
}
[并行运行时,一次要说25个线程,而每个线程都有自己的ScriptEngine(以及上面提到的所有预编译的东西)将花费很长的时间执行,而CPU使用率却很少(8-10%总计),并且jmc / jvisualvm中没有重大阻塞。线程将显示它们已经阻塞了相当数量(按明智的选择),但是切片是如此之细,以至于我在单击线程时都无法看到它们。
[大多数时候,我单击所有线程时,它们都显示它们在MethodHandleNatives.setCallSiteTargetNormal中。
我已经尝试了一些方法:1.单引擎,不同的环境。即使所有线程都已预编译,我也可以在它们之间看到阻塞。线程将等待(如应有的那样),然后再根据我的判断调用各个字节码片段。这不是一个可行的解决方案。
这是创建ScriptEngine并使用“ utility.js”对其进行预填充的方式(我将它们填充到池中的代码已省略以使其简短):
/**
* Creates a PreCompiledScriptEngine which will contain a ScriptEngine + Pre-compiled utility.js
*/
private PreCompiledScriptEngine createScriptEngine() {
String source = new Scanner(this.getClass().getClassLoader().getResourceAsStream(UTILITY_SCRIPT)).useDelimiter("\\Z").next();
try {
totalEngines.getAndAdd(1);
ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine();
return new PreCompiledScriptEngine(engine, ((Compilable) engine).compile(source));
}
catch (ScriptException e) {
Logger.error(e);
}
return null;
}
/**
* Small helper class to group a ScriptEngine and a CompiledScript (of utility.js) together
*/
public class PreCompiledScriptEngine {
private ScriptEngine scriptEngine;
private CompiledScript compiledScript;
PreCompiledScriptEngine(ScriptEngine scriptEngine, CompiledScript compiledScript) {
this.scriptEngine = scriptEngine;
this.compiledScript = compiledScript;
}
public ScriptEngine getScriptEngine() {
return scriptEngine;
}
/**
* This method will return the utility.js compiled runtime against our engine.
*
* @return CompiledScript version of utility.js
*/
public CompiledScript getCompiledScript() {
return compiledScript;
}
}
这是我执行用户特定JavaScript的方式:
public Object executeUserScript(String script, String scriptFunction, Object[] parameters) {
try {
// Create a brand new context
PreCompiledScriptEngine preCompiledScriptEngine = obtainFromMyScriptEnginePool();
ScriptEngine engine = preCompiledScriptEngine.getScriptEngine();
ScriptContext context = new SimpleScriptContext();
context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
// Evaluate the pre-compiled utility.js in our new context
preCompiledScriptEngine.getCompiledScript().eval(context);
// Evaluate the specific user script in this context too
engine.eval(script, context);
//get the JS function the user wants to call
JSObject jsObject = (JSObject) context.getAttribute(scriptFunction, ScriptContext.ENGINE_SCOPE);
// Call the JS function with the parameters
return jsObject.call(null, parameters);
}
catch (ScriptException e) {
Logger.error("generated", e);
throw new RuntimeException(e.getMessage());
}
}
我期望的是,如果我的线程池用尽了计算机上的可用资源并显示出较低的性能,那么CPU使用率将为100%,但是相反,我看到的是较低的CPU和较低的性能:(我可以不太清楚我在哪里出错了,或者为什么它这么慢而又没有明显的资源消耗。
我从JVisualVM捕获堆栈跟踪时刚刚注意到的一件事是,我的所有线程都表现出这种情况:我允许用户定义的Java脚本调用Utility.js函数,该函数本质上是“执行另一个脚本” ,所有堆栈跟踪似乎都是从此嵌套调用到另一个脚本的。在我的设置中,它将使用相同的线程,并使用新上下文再次使用该线程中的相同引擎。我认为这与以前相同,不需要进一步编译?
我已经看过的相关文章:What is the difference between anonymous and inline functions in JavaScript?和Nashorn inefficiency
编辑:从更深入的角度来看,主要是eval()是从编译脚本内部发生的,但并非总是如此,有关特定情况的某些事情必须使它无法直接重新调用而不调用setTarget(),这最终会花费更多时间。
有趣的是,当线程对本地JVM方法进行这些调用时,它们并没有显示它们正在阻塞,因此很难看到我所看过的每个工具在哪里花费了时间。
我在具有16个VCPU VM的2018年老式硬件上看到类似的问题。
我们的症状是:
希望这对某人有帮助-如果有人有见识,请回复。