【问题标题】:Can anyone explain this unexpected V8 JavaScript performance behaviour?谁能解释这种意外的 V8 JavaScript 性能行为?
【发布时间】:2020-01-06 17:19:21
【问题描述】:

更新(2020 年 3 月 2 日)

事实证明,我的示例中的编码以正确的方式构建,以摆脱 V8 JavaScript 引擎中已知的性能悬崖...

有关详细信息,请参阅bugs.chromium.org 上的讨论。这个错误现在正在处理中,应该会在不久的将来修复。

更新(2020 年 1 月 9 日)

我尝试将具有下述行为方式的代码隔离到一个单页 Web 应用程序中,但这样做时,行为消失了 (??)。但是,下面描述的行为仍然存在于完整应用程序的上下文中。

也就是说,我已经优化了分形计算编码,这个问题在正式版中不再是问题。如果有人有兴趣,可以使用显示此问题的 JavaScript 模块here

概述

我刚刚完成了一个基于 Web 的小型应用程序,用于比较基于浏览器的 JavaScript 与 Web Assembly 的性能。此应用计算 Mandelbrot 集图像,然后当您将鼠标指针移到该图像上时,会动态计算相应的 Julia 集并显示计算时间。

您可以在使用 JavaScript(按“j”)或 WebAssembly(按“w”)之间切换来执行计算并比较运行时。

点击here查看正在运行的应用程序

然而,在编写这段代码时,我发现了一些意想不到的奇怪的 JavaScript 性能行为......

问题总结

  1. 这个问题似乎是 Chrome 和 Brave 中使用的 V8 JavaScript 引擎特有的。此问题不会出现在使用 SpiderMonkey (Firefox) 或 JavaScriptCore (Safari) 的浏览器中。我无法在使用 Chakra 引擎的浏览器中对此进行测试

  2. 此 Web 应用程序的所有 JavaScript 代码均已编写为 ES6 Modules

  3. 我尝试使用传统的function 语法而不是新的 ES6 箭头语法重写所有函数。不幸的是,这并没有任何明显的区别

性能问题似乎与创建 JavaScript 函数的范围有关。在这个应用程序中,我调用了两个部分函数,​​每个函数都返回另一个函数。然后我将这些生成的函数作为参数传递给在嵌套的for 循环中调用的另一个函数。

相对于它执行的函数,for 循环似乎创建了类似于它自己的作用域的东西(虽然不确定它是一个成熟的作用域)。然后,跨这个范围(?)边界传递生成的函数是昂贵的。

基本编码结构

每个偏函数接收鼠标指针在Mandelbrot Set图像上位置的X或Y值,并在计算对应的Julia集时返回要迭代的函数:

const makeJuliaXStepFn = mandelXCoord => (x, y) => mandelXCoord + diffOfSquares(x, y)
const makeJuliaYStepFn = mandelYCoord => (x, y) => mandelYCoord + (2 * x * y)

这些函数在以下逻辑中被调用:

  • 用户将鼠标指针移动到触发mousemove 事件的 Mandelbrot 集的图像上
  • 鼠标指针的当前位置被翻译到Mandelbrot集合的坐标空间,并将(X,Y)坐标传递给函数juliaCalcJS计算对应的Julia Set。

    李>
  • 在创建任何特定的 Julia Set 时,会调用上述两个偏函数来生成创建 Julia Set 时要迭代的函数

  • 嵌套的for 循环然后调用函数juliaIter 来计算Julia 集中每个像素的颜色。完整编码可见here,但基本逻辑如下:

    const juliaCalcJS =
      (cvs, juliaSpace) => {
        // Snip - initialise canvas and create a new image array
    
        // Generate functions for calculating the current Julia Set
        let juliaXStepFn = makeJuliaXStepFn(juliaSpace.mandelXCoord)
        let juliaYStepFn = makeJuliaYStepFn(juliaSpace.mandelYCoord)
    
        // For each pixel in the canvas...
        for (let iy = 0; iy < cvs.height; ++iy) {
          for (let ix = 0; ix < cvs.width; ++ix) {
            // Translate pixel values to coordinate space of Julia Set
            let x_coord = juliaSpace.xMin + (juliaSpace.xMax - juliaSpace.xMin) * ix / (cvs.width - 1)
            let y_coord = juliaSpace.yMin + (juliaSpace.yMax - juliaSpace.yMin) * iy / (cvs.height - 1)
    
            // Calculate colour of the current pixel
            let thisColour = juliaIter(x_coord, y_coord, juliaXStepFn, juliaYStepFn)
    
            // Snip - Write pixel value to image array
          }
        }
    
        // Snip - write image array to canvas
      }
    
  • 如您所见,在for 循环外调用makeJuliaXStepFnmakeJuliaYStepFn 返回的函数将传递给juliaIter,然后由它完成计算当前像素颜色的所有艰苦工作

当我看到这个代码结构时,起初我认为“这很好,一切都很好;所以这里没有错”

除了有。性能比预期的要慢很多...

意外的解决方案

随之而来的是许多挠头和摆弄......

过了一会儿,我发现如果我将函数 juliaXStepFnjuliaYStepFn 的创建移动到外部或内部 for 循环中,那么性能会提高 2 到 3 倍...

哇!?

所以,代码现在看起来像这样

const juliaCalcJS =
  (cvs, juliaSpace) => {
    // Snip - initialise canvas and create a new image array

    // For each pixel in the canvas...
    for (let iy = 0; iy < cvs.height; ++iy) {
      // Generate functions for calculating the current Julia Set
      let juliaXStepFn = makeJuliaXStepFn(juliaSpace.mandelXCoord)
      let juliaYStepFn = makeJuliaYStepFn(juliaSpace.mandelYCoord)

      for (let ix = 0; ix < cvs.width; ++ix) {
        // Translate pixel values to coordinate space of Julia Set
        let x_coord = juliaSpace.xMin + (juliaSpace.xMax - juliaSpace.xMin) * ix / (cvs.width - 1)
        let y_coord = juliaSpace.yMin + (juliaSpace.yMax - juliaSpace.yMin) * iy / (cvs.height - 1)

        // Calculate colour of the current pixel
        let thisColour = juliaIter(x_coord, y_coord, juliaXStepFn, juliaYStepFn)

        // Snip - Write pixel value to image array
      }
    }

    // Snip - write image array to canvas
  }

我本以为这种看似微不足道的更改效率会有所降低,因为每次迭代 for 循环时都会重新创建一对不需要更改的函数。然而,通过在for 循环内移动函数声明,这段代码的执行速度提高了 2 到 3 倍!

谁能解释这种行为?

谢谢

【问题讨论】:

  • 难住了,但我确实很喜欢查看您的网站和那里的应用程序。真是好东西!
  • 感谢@Calculuswhiz,这似乎是 Chrome/Brave 特有的问题。 Safari 和 Firefox 似乎没有受到影响。我会相应地更新帖子
  • 这是一个非常详细的总结......你有什么理由在一般编程问答网站上提交基本上是 V8 票证,而不是在the V8 issue tracker 上?
  • 他确实在 V8 跟踪器上发布了一个问题。这是那里的第一个
  • 我的猜测是,将所有内容都包含在迭代中可以简化优化器的依赖关系图,从而能够生成更好的代码。 v8 profiler 可能会更清楚地了解正在发生的事情。

标签: javascript performance scope


【解决方案1】:

我的代码设法从 V8 JavaScript 引擎中已知的性能悬崖上掉下来...

问题的详细信息和修复方法在bugs.chromium.org进行了描述

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多