谁能解释这种意外的V8 JavaScript性能行为?

克里斯·W

更新(2020年3月2日)

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

有关详细信息,请参见bugs.chromium.org上的讨论现在正在修复此错误,并应在不久的将来修复。

更新(2020年1月9日)

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

就是说,自那以来,我已经优化了分形计算编码,这个问题在实时版本中不再是问题。如果有人对此感兴趣,则可以在此处使用表明此问题的JavaScript模块。

总览

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

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

点击此处查看正在运行的应用

但是,在编写此代码时,我发现了一些意想不到的奇怪的JavaScript性能行为...

问题总结

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

  2. 该Web应用程序的所有JavaScript代码均已编写为ES6模块

  3. 我尝试使用传统function语法而不是新的ES6箭头语法重写所有功能不幸的是,这没有任何明显的不同

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

相对于其执行的功能,似乎for循环创建了类似于其自身作用域的内容(尽管不确定其作用域是否成熟)。然后,将生成的函数传递到该scope(?)边界是昂贵的。

基本编码结构

每个部分函数接收鼠标指针在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集。

  • 创建任何特定的Julia集时,将调用上述两个部分函数以生成创建Julia集时要迭代的函数

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

    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
      }
    
  • 如您所见,调用makeJuliaXStepFn循环makeJuliaYStepFn返回的函数for将传递给juliaIter函数,然后将完成所有艰苦的工作来计算当前像素的颜色

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

除了那里。性能比预期慢得多。

意外的解决方案

随后,很多人挠头和摆弄。

一段时间后,我发现,如果我移动的函数的创建juliaXStepFnjuliaYStepFn内部或者外部或内部for环,则性能通过的2和3之间的因子提高...

WHAAAAAAT!?

因此,代码现在看起来像这样

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倍!

谁能解释这种行为?

谢谢

克里斯·W

我的代码成功摆脱了V8 JavaScript引擎的性能瓶颈。

有关问题的详细信息和修复程序,请参见bugs.chromium.org。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

CPU缓存的这种性能行为的说明

来自分类Dev

谁能解释MariaDB的这种怪异行为?

来自分类Dev

谁能解释这个javaScript

来自分类Dev

正则表达式性能问题-谁能解释这种正则表达式缓慢的方式

来自分类Dev

谁能解释一下另一个函数中函数的这种行为?

来自分类Dev

谁能解释一下shutil.rmtree和shutil.copytree的这种怪异行为?

来自分类Dev

SQL:奇怪的查询性能行为

来自分类Dev

Java BigDecimal奇怪的性能行为

来自分类Dev

SQL:奇怪的查询性能行为

来自分类Dev

谁能解释一下Perl程序的行为

来自分类Dev

R中意外的套用功能行为

来自分类Dev

V8中的Javascript奇怪的性能

来自分类Dev

您能解释这种超载解决方案的行为吗?

来自分类Dev

使用OpenGL和GLSL的SSAO算法的奇怪性能行为

来自分类Dev

.NET字典插入的怪异性能行为

来自分类Dev

谁能解释在分组数据集上运行CombineGroup和reduceGroup转换之间的不同行为?

来自分类Dev

v8性能准则

来自分类Dev

Node.js v8 HOLEY 数组意外行为

来自分类Dev

如何解释我的Scheme代码的这种意外行为?

来自分类Dev

谁能解释这种语法?char * seats [14] [7];

来自分类Dev

在V8或其他C ++ JavaScript解释器中继续

来自分类Dev

在V8或其他C ++ JavaScript解释器中继续

来自分类Dev

您能解释一下这种怪异的函数声明行为吗?

来自分类Dev

您能解释一下这种怪异的函数声明行为吗?

来自分类Dev

Java Applet在最新JRE(7.55+)上的奇怪性能行为

来自分类Dev

谁能解释一下字符串和数字比较在javascript中是如何工作的?

来自分类Dev

predsort / 3的可能行为

来自分类Dev

奇怪的窗口功能行为

来自分类Dev

Android拆分功能行为

Related 相关文章

热门标签

归档