首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >有人能解释一下这种出乎意料的V8 JavaScript性能行为吗?

有人能解释一下这种出乎意料的V8 JavaScript性能行为吗?
EN

Stack Overflow用户
提问于 2020-01-06 17:19:21
回答 1查看 198关注 0票数 8

更新(2020年3月2日)

事实证明,在我的示例中,编码是以正确的方式构造的,可以在V8 JavaScript引擎中从已知的性能悬崖上掉下来……

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

更新(2020年1月9日)

我试图将以下面描述的方式运行的编码隔离到一个页面Web应用程序中,但是这样做,这种行为就消失了(??)。然而,下文所述的行为在全面适用的情况下仍然存在。

尽管如此,我已经优化了分形计算编码,这个问题在实时版本中不再是一个问题。如果有人感兴趣,那么显示此问题的JavaScript模块仍然可用这里

概述

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

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

单击这里查看工作应用程序

然而,在编写这段代码时,我发现了一些出乎意料的奇怪的JavaScript性能行为。

问题摘要

  1. 这个问题似乎是针对用于Chrome和Brave的V8 JavaScript引擎的。在使用SpiderMonkey (火狐)或JavaScriptCore (Safari)的浏览器中没有出现此问题。我无法使用脉轮引擎在浏览器中测试这一点。
  2. 这个网络应用程序的所有JavaScript代码都写成了ES6模
  3. 我尝试使用传统的function语法而不是新的ES6箭头语法重写所有函数。不幸的是,这并没有造成任何明显的差别。

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

相对于它执行的函数,for循环似乎创建了类似于它自己的作用域的东西(但不确定它是一个完整的作用域)。然后,将生成的函数传递到这个作用域(?)边界太贵了。

基本编码结构

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

代码语言:javascript
代码运行次数:0
运行
复制
const makeJuliaXStepFn = mandelXCoord => (x, y) => mandelXCoord + diffOfSquares(x, y)
const makeJuliaYStepFn = mandelYCoord => (x, y) => mandelYCoord + (2 * x * y)

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

  • 用户将鼠标指针移动到Mandelbrot集的图像上,触发mousemove事件。
  • 将鼠标指针的当前位置转换为Mandelbrot集的坐标空间,将(X,Y)坐标传递给函数juliaCalcJS,计算相应的朱莉娅集。
  • 在创建任何特定的Julia集时,调用上述两个部分函数来生成在创建Julia集时要迭代的函数。
  • 然后,一个嵌套的for循环调用函数juliaIter来计算朱莉娅集中每个像素的颜色。完整的编码可以看到这里,但基本逻辑如下: const juliaCalcJS = (cvs,juliaSpace) => { // Set初始化画布,并为画布中的每个像素创建一个新的图像数组//生成函数来计算当前的朱莉娅集let juliaXStepFn = makeJuliaXStepFn(juliaSpace.mandelXCoord) let juliaYStepFn = makeJuliaYStepFn(juliaSpace.mandelYCoord) /.for (设iy = 0;iy < cvs.height;++iy) { for (设ix = 0;ix < cvs.width;++ix) { //将像素值转换为x_coord = juliaSpace.xMin + (juliaSpace.xMax - juliaSpace.xMin) * ix / (cvs.width - 1)让y_coord = juliaSpace.yMin + (juliaSpace.yMax - juliaSpace.yMin) * iy / (cvs.height - 1) /计算当前像素的颜色让thisColour = juliaIter(x_coord,y_coord,juliaXStepFn,x_coord,y_coord,juliaXStepFn,( juliaYStepFn) // Snip -将像素值写入图像数组}} // Snip -将图像数组写入画布}
  • 如您所见,调用makeJuliaXStepFnmakeJuliaYStepFnfor循环外返回的函数将传递给juliaIter,后者将完成计算当前像素颜色的所有艰苦工作。

当我看这个代码的结构时,我一开始想:“这很好,一切都很好,所以这里没什么问题。”

只是有。表演比预期要慢得多.

意外解决方案

很多人抓着头到处乱动.

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

天啊!?

所以,代码现在看起来如下

代码语言:javascript
代码运行次数:0
运行
复制
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倍!

有人能解释一下这种行为吗?

谢谢

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-03-02 15:06:11

我的代码在V8 JavaScript引擎中掉下了一个已知的性能悬崖.

问题和修复的细节在bugs.chromium.org上进行了描述。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59616335

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档