简体   繁体   English

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

[英]Can anyone explain this unexpected V8 JavaScript performance behaviour?

Update (Mar 2nd, 2020)更新(2020 年 3 月 2 日)

It turns out that the coding in my example here was structured in just the right way to fall off a known performance cliff in the V8 JavaScript engine...事实证明,我的示例中的编码以正确的方式构建,以摆脱 V8 JavaScript 引擎中已知的性能悬崖……

See the discussion over on bugs.chromium.org for the details.有关详细信息,请参阅bugs.chromium.org上的讨论。 This bug is now being worked on and should be fixed in the near future.这个错误现在正在处理中,应该在不久的将来修复。

Update (Jan 9th, 2020)更新(2020 年 1 月 9 日)

I tried to isolate the coding that behaves in the manner described below into a single page Web app, but in doing so, the behaviour disappeared(??).我试图将下面描述的行为的编码隔离到一个单页 Web 应用程序中,但这样做时,行为消失了(??)。 However, the behaviour described below does still exist in the context of the full application.但是,下面描述的行为在完整应用程序的上下文中仍然存在。

That said, I have since optimised the fractal calculation coding and this problem is no longer an issue in the live version.也就是说,我已经优化了分形计算编码,这个问题在实时版本中不再是问题。 Should anyone be interested, the JavaScript module that manifests this problem is still available here如果有人感兴趣,这里仍然提供显示此问题的 JavaScript 模块

Overview概述

I've just completed a small Web-based app to compare the performance of browser-based JavaScript with Web Assembly.我刚刚完成了一个基于 Web 的小型应用程序,以比较基于浏览器的 JavaScript 与 Web Assembly 的性能。 This app calculates a Mandelbrot Set image, then as you move the mouse pointer over that image, the corresponding Julia Set is dynamically calculated and the calculation time is displayed.此应用程序计算 Mandelbrot Set 图像,然后当您将鼠标指针移到该图像上时,会动态计算相应的 Julia Set 并显示计算时间。

You can switch between using JavaScript (press 'j') or WebAssembly (press 'w') to perform the calculation and compare runtimes.您可以在使用 JavaScript(按“j”)或 WebAssembly(按“w”)之间切换来执行计算和比较运行时。

Click here to see the working app单击此处查看正在运行的应用程序

However, in writing this code, I discovered some unexpectedly strange JavaScript performance behaviour...然而,在编写这段代码时,我发现了一些出乎意料的奇怪的 JavaScript 性能行为......

Problem Summary问题总结

  1. This problem seems to be specific to the V8 JavaScript engine used in Chrome and Brave.这个问题似乎是 Chrome 和 Brave 中使用的 V8 JavaScript 引擎所特有的。 This problem does not appear in browsers using SpiderMonkey (Firefox) or JavaScriptCore (Safari).在使用 SpiderMonkey (Firefox) 或 JavaScriptCore (Safari) 的浏览器中不会出现此问题。 I have not been able to test this in a browser using the Chakra engine我无法在使用 Chakra 引擎的浏览器中对此进行测试

  2. All the JavaScript code for this Web app has been written as ES6 Modules此 Web 应用程序的所有 JavaScript 代码都已编写为ES6 模块

  3. I've tried rewriting all the functions using the traditional function syntax rather than the new ES6 arrow syntax.我尝试使用传统的function语法而不是新的 ES6 箭头语法重写所有函数。 Unfortunately, this does not make any appreciable difference不幸的是,这没有任何明显的区别

The performance problem seems to relate to the scope within which a JavaScript function is created.性能问题似乎与创建 JavaScript 函数的范围有关。 In this app, I call two partial functions, each of which gives me back another function.在这个应用程序中,我调用了两个部分函数,​​每个函数都返回另一个函数。 I then pass these generated functions as arguments to another function that is called inside a nested for loop.然后我将这些生成的函数作为参数传递给另一个在嵌套for循环中调用的函数。

Relative to the function within which it executes, it appears that a for loop creates something resembling its own scope (not sure its a full-blown scope though).相对于它执行的函数, for循环似乎创建了类似于它自己的作用域的东西(虽然不确定它是一个完整的作用域)。 Then, passing generated functions across this scope(?) boundary is expensive.然后,跨越这个范围(?)边界传递生成的函数是昂贵的。

Basic Coding Structure基本编码结构

Each partial function receives the X or Y value of the mouse pointer's position over the Mandelbrot Set image, and returns the function to be iterated when calculating the corresponding Julia set:每个偏函数接收鼠标指针在 Mandelbrot Set 图像上的位置的 X 或 Y 值,并在计算相应的 Julia 集时返回要迭代的函数:

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

These functions are called within the following logic:这些函数在以下逻辑中被调用:

  • The user moves the mouse pointer over the image of the Mandelbrot Set triggering the mousemove event用户将鼠标指针移动到触发mousemove事件的 Mandelbrot 集的图像上
  • The current location of the mouse pointer is translated to the coordinate space of Mandelbrot set and the (X,Y) coordinates are passed to function juliaCalcJS to calculate the corresponding Julia Set.将鼠标指针的当前位置转换到 Mandelbrot 集的坐标空间,并将 (X,Y) 坐标传递给函数juliaCalcJS以计算相应的 Julia Set。

  • When creating any particular Julia Set, the above two partial functions are called to generate the functions to be iterated when creating the Julia Set创建任何特定的 Julia Set 时,会调用上述两个偏函数来生成创建 Julia Set 时要迭代的函数

  • A nested for loop then calls function juliaIter to calculate the colour of every pixel in the Julia set.然后嵌套的for循环调用函数juliaIter来计算 Julia 集中每个像素的颜色。 The full coding can be seen here , but the essential logic is as follows:完整的编码可以在这里看到,但基本逻辑如下:

     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 }
  • As you can see, the functions returned by calling makeJuliaXStepFn and makeJuliaYStepFn outside the for loop are passed to juliaIter which then does all the hard work of calculating the colour of the current pixel如您所见,在for循环外调用makeJuliaXStepFnmakeJuliaYStepFn返回的函数被传递给juliaIter ,然后它会完成计算当前像素颜色的所有繁重工作

When I looked at this structure of code, at first I thought "This fine, it all works nicely; so nothing wrong here"当我看到这种代码结构时,起初我想“这很好,一切都很好;所以这里没有错”

Except there was.除了有。 The performance was much slower than expected...性能比预期的要慢得多......

Unexpected Solution意外的解决方案

Much head scratching and fiddling around followed...随之而来的是许多头刮和摆弄......

After a while, I discovered that if I move the creation of functions juliaXStepFn and juliaYStepFn inside either the outer or inner for loops, then the performance improves by a factor of between 2 and 3...一段时间后,我发现如果我将函数juliaXStepFnjuliaYStepFn的创建移动到外部或内部for循环中,那么性能会提高 2 到 3 倍......

WHAAAAAAT!?哇哦!?

So, the code now looks like this所以,代码现在看起来像这样

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
  }

I would have expected this seemingly insignificant change to be somewhat less efficient, because a pair of functions that do not need to change are being recreated each time we iterate the for loop.我原以为这种看似微不足道的更改会降低效率,因为每次迭代for循环时都会重新创建一对不需要更改的函数。 Yet, by moving the function declarations inside the for loop, this code executes between 2 and 3 times faster!然而,通过在for循环内移动函数声明,这段代码的执行速度提高了 2 到 3 倍!

Can anyone explain this behaviour?谁能解释这种行为?

Thanks谢谢

My code managed to fall off a known performance cliff in the V8 JavaScript engine...我的代码在 V8 JavaScript 引擎中设法摆脱了已知的性能悬崖......

The details of the problem and the fix are described on bugs.chromium.org bugs.chromium.org上描述了问题的详细信息和修复

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM