事实证明,在我的示例中,编码是以正确的方式构建的,以摆脱V8 JavaScript引擎中的已知性能悬崖...
有关详细信息,请参见bugs.chromium.org上的讨论。现在正在修复此错误,并应在不久的将来修复。
我试图将以下面描述的方式运行的代码隔离到一个单页Web应用程序中,但是这样做后,该行为消失了(??)。但是,下面描述的行为在整个应用程序的上下文中仍然存在。
就是说,自那以来,我已经优化了分形计算编码,这个问题在实时版本中不再是问题。如果有人对此感兴趣,则可以在此处使用表明此问题的JavaScript模块。
我刚刚完成了一个小型的基于Web的应用程序,以将基于浏览器的JavaScript与Web Assembly的性能进行比较。此应用程序将计算一个Mandelbrot Set图像,然后将鼠标指针移到该图像上,将动态计算相应的Julia Set并显示计算时间。
您可以在使用JavaScript(按“ j”)或WebAssembly(按“ w”)之间切换以执行计算并比较运行时间。
点击此处查看正在运行的应用
但是,在编写此代码时,我发现了一些意想不到的奇怪的JavaScript性能行为...
此问题似乎特定于Chrome和Brave中使用的V8 JavaScript引擎。使用SpiderMonkey(Firefox)或JavaScriptCore(Safari)的浏览器中不会出现此问题。我无法使用Chakra引擎在浏览器中对此进行测试
该Web应用程序的所有JavaScript代码均已编写为ES6模块
我尝试使用传统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
该函数,然后将完成所有艰苦的工作来计算当前像素的颜色
当我查看代码的这种结构时,起初我以为“很好,一切都很好;所以这里没有错”
除了那里。性能比预期慢得多。
随后,很多人挠头和摆弄。
一段时间后,我发现,如果我移动的函数的创建juliaXStepFn
和juliaYStepFn
内部或者外部或内部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倍!
谁能解释这种行为?
谢谢
我的代码成功摆脱了V8 JavaScript引擎的性能瓶颈。
有关问题的详细信息和修复程序,请参见bugs.chromium.org。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句