我已经尝试了Javascript国际象棋引擎一段时间了。是的,我知道(咯咯笑),而不是那种最好的平台。这是一个小项目,我很喜欢学术练习,并且对接近已编译语言速度的挑战也很感兴趣。 Javascript还存在其他古怪的挑战,例如缺少64位整数,使其不适合象棋,但也自相矛盾。
不久前,我意识到对结构,函数参数等保持谨慎非常重要。在象棋编程中,一切都很重要,但是当通过Chrome中的Javascript使用JIT编译器(V8 Turbofan)时,似乎很重要。
通过一些痕迹,我看到一些急切的DEOPT,我在弄清楚如何避免时遇到了麻烦。
渴望渴望,错误的地图
跟踪引用的代码:
if (validMoves.length) { ...do some stuff... }
跟踪直接指向IF条件的validMoves.length参数。 validMoves只能是空数组[]或移动对象数组[{Move},{Move},...]
空数组[]会引发DEOPT吗?
顺便说一句,我有很多懒惰的和软的DEOPT,但是如果我理解正确的话,它们并不是那么关键,而只是V8在最终对其进行优化之前如何将其围绕我的代码的一部分。在--trace-opt中,具有软懒惰DEOPT的功能似乎最终由Turbofan进行了优化,也许从长远来看不会对性能造成太大影响。 (为此,急切的DEOPT功能似乎也最终得到了优化。)这是否是正确的评估?
最后,我有时发现通过将显示DEOPT的函数分解为多个较小的函数调用,我获得了显着的性能提升。由此推断,较大,更复杂的功能难以优化,而通过分解它们,较小的分隔功能也得到了优化,从而为我带来了收益。听起来合理吗?
最佳答案
缺乏64位整数
好吧,现在有BigInts了:-)
(但是在大多数引擎/情况下,它们还不适合高性能操作。)
空数组[]会引发DEOPT吗?
一般没有。但是,数组有不同的内部表示形式,因此可能是或可能不是。
[懒惰,温柔,渴望...]这是正确的评估吗?
通常是。通常,您不必担心取消启用,特别是对于长期运行的程序,这些程序在早期会遇到一些取消启用的情况。对于--trace-deopt
报告的所有形容词都是如此-这些都是内部细节。 (“ eager”和“ lazy”彼此直接相反,仅表示必须取消优化的功能的激活是否是栈顶的。“ soft”是取消选择的一个特殊原因,即缺少类型反馈,尽管缺少类型反馈,V8还是选择取消优化而不是生成“优化”代码,这根本就不会非常优化。)
作为JavaScript开发人员,在极少数情况下,您可能想关心deopts。一个例子是,当您遇到一遍又一遍相同的取消选择的情况。这是V8中发生的错误;这些“ deopt循环”很少见,但偶尔会发生。如果您发现这种情况,请file a bug并提供复制说明。
另一种情况是,每个CPU周期都很重要,尤其是在启动期间/短期运行的应用程序中,并且某些昂贵的功能由于可能可以避免的原因而被取消优化。但这似乎不是您的情况。
[分拆功能...]听起来合理吗?
分解功能可能是有益的,是的。尤其是当您开始使用的功能非常丰富时。通常,各种规模的功能都会得到优化。显然,较大的函数需要更长的时间进行优化。这是一个棘手的领域,没有简单的答案。如果函数太小,那么对性能也不会有帮助。 V8会执行一些内联,但是决策是基于自然并不总是完美的试探法。以我的经验,手动拆分函数可以为长时间运行的循环(将循环放入其自己的函数中)带来特别的回报。
编辑:按要求详细说明最后一点,这是一个示例:而不是
function big() {
for (...) {
// long-running loop
}
/* lots more stuff... */
}
您将其拆分为:
function loop() {
for (...) {
// same loop as before
}
}
function outer() {
loop();
/* same other stuff as before */
}
对于短循环,这是完全没有必要的,但是如果在循环中花费了大量时间,并且函数的整体大小很大,则此拆分将使优化可以在更细粒度的块中进行,而更少(“软”)拒绝。
明确地说:我只建议在遇到特定问题时执行此操作(例如:
--trace-opt
告诉您最大的功能已优化两次或多次,每次需要一秒钟)。请不要轻信阅读此答案,认为“每个人都应始终分开其职能”,这根本不是我要说的。在功能强大的极端情况下,将其拆分可能是有益的。