正在研究 Javascript DEOPT 原因 Chrome 79 一个宠物项目

Researching Javascript DEOPT reasons in Chrome 79 for a pet project

我一直在修补 Javascript 国际象棋引擎。是的是的我知道(笑),不是那种东西的最佳平台。这是一个有点喜欢的项目,我很享受学术练习,并且对接近编译语言速度的挑战很感兴趣。 Javascript 中还有其他古怪的挑战,比如缺少 64 位整数,这使得它不适合国际象棋,但矛盾的是也很有趣。

不久前我意识到在构造、函数参数等方面要小心谨慎。在国际象棋编程中一切都很重要,但在使用 JIT 编译器(V8 Turbofan)时似乎很重要=33=] 在 Chrome.

通过一些追踪,我看到了一些急切的 DEOPT,我很难弄清楚如何避免。

DEOPT eager,地图错误

跟踪引用的代码:

if (validMoves.length) { ...do some stuff... }

跟踪直接指向 IF 条件的 validMoves.length 参数。 validMoves 只是空数组 [] 或移动对象数组 [{Move},{Move},...]

空数组 [] 会启动 DEOPT 吗?

顺便说一句,我有很多懒惰的和软的 DEOPT,但如果我理解正确的话,这些并不是那么重要,只是 V8 在最终优化代码之前如何绕过我的代码的一部分;在 --trace-opt 中,带有软延迟 DEOPT 的函数似乎最终会被 Turbofan 优化,并且可能不会在长期 运行 中对性能造成太大影响。 (就此而言,热切的 DEOPT 函数似乎最终也得到了重新优化。)这是一个正确的评估吗?

最后,我有时发现,通过将已显示 DEOPT 的函数分解为多个较小的函数调用,我获得了显着的性能提升。由此我推断,更大更复杂的函数在优化时遇到了困难,并且通过分解它们,较小的分隔函数正在被优化,从而满足了我的收益。听起来合理吗?

the lack of 64bit integers

嗯,现在有 BigInts :-) (但在大多数情况下 engines/cases 它们还不适合高性能操作。)

Would an empty array [] kick off a DEOPT?

一般不会。然而,数组有不同的内部表示,所以这可能是也可能不是那里发生的事情。

[lazy, soft, eager...] Is this a correct assessment?

一般是的。通常您不必担心 deopts,特别是对于早期经历过几次 deopts 的长 运行 程序。 --trace-deopt 报告的所有形容词都是如此——这些都只是内部细节。 ("eager" 和 "lazy" 是彼此直接相反的,只是表示必须去优化的函数的激活是否在栈顶。 "soft" 是一个特殊的原因对于 deopt,即缺乏类型反馈,尽管缺乏类型反馈,V8 选择去优化而不是生成 "optimized" 代码,这根本不会非常优化。)

作为 JavaScript 开发人员,在极少数情况下,您可能想要关心 deopts。一个例子是当您遇到相同的 deopt 一遍又一遍地发生的情况。当它发生时,这是 V8 中的一个错误;这些 "deopt loops" 很少见,但偶尔会发生。如果您发现了这样的情况,请file a bug附上复现说明。

另一种情况是每个 CPU 周期都很重要,尤其是在启动期间/简而言之 - 运行 应用程序,并且一些昂贵的功能由于可能可以避免的原因而被取消优化。不过你的情况似乎并非如此。

[breaking up functions...] Does that sound reasonable?

分解功能是有益的,是的;特别是如果您开始使用的功能很大。通常,各种规模的功能都会得到优化;显然,更大的函数需要更长的时间来优化。这是一个棘手的领域,没有简单的答案;如果函数 小,那么这对性能也没有帮助。 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 */
}

对于一个短循环,这是完全没有必要的,但如果在循环中花费大量时间 并且 函数的整体规模很大,那么这种拆分允许优化发生在更细粒度的块中并且更少("soft")deopts。

并且非常清楚:我只建议您在遇到特定问题时这样做(例如:--trace-opt 告诉您最大的功能已优化两次或更多次,每次花费一秒钟) .请不要离开阅读这个答案思考 "everyone should always split their functions",这根本不是我要说的。在 极端 巨大 函数的情况下,拆分它们 可以 是有益的。