为什么 "Self Time" 在一个有效的空函数中如此之高?

Why is "Self Time" so high in an effectively empty function?

我有一个计算量很大的函数,它在循环中被调用了很多次:

function func() { // Some fluff
  for(let i = 0; i < 1000; i++) {
    i *= 10
    i /= 10
  }
}

function run() {
  for(let i = 0; i < 100000; i++) {
    func()
  }
}

run()

当我使用 Chrome 的 DevTools 分析这个脚本时,我得到了这个:

run 在 1015 毫秒的总时间中有 887 毫秒的自身时间,尽管它唯一做的就是重复调用 func.

我希望 func 有大部分的自我时间,因为它是叶函数。

这是为什么?

(此处为 V8 开发人员。)

the function was automatically inlined after some time when it became "hot".

正确。一旦 run 被优化,优化器决定将 func 内联到其中。之后,就profiler而言,所有的时间都花在了run.
(为了验证这一点,运行 d8node 中的片段与 --trace-turbo-inlining。)
旁注:在这种情况下,获得 run 的优化代码比平时花费的时间要长一些,因为该函数永远不会 returns 再次被调用(这是切换到优化代码的最佳时间)。系统等待它发生,当它没有发生时,run 最终被“堆栈替换”。这是一种典型模式,经常出现在小型测试和基准测试中,很少出现在实际代码中。

Doesn't this just show that performing 100000 function calls are more expensive than a 1000 iterations of two simple arithmetic operations -- which makes sense?

不,它没有显示;这只是一个人如何被这个微基准误导的一种特殊方式。

就个人而言,我有点失望地看到(使用 --print-opt-code)编译器没有意识到 i *= 10; i /= 10; 是空操作,可以完全删除。那将是在这里被误导的另一种好方法。哦,好吧,可能有一些原因导致编译器更难确定该转换既适用又安全...