Chr*_*s W 8 javascript performance scope
事实证明,我的示例中的编码以正确的方式构建,以摆脱 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循环似乎创建了类似于它自己的作用域的东西(虽然不确定它是一个完整的作用域)。然后,跨越这个范围(?)边界传递生成的函数是昂贵的。
每个偏函数接收鼠标指针在 Mandelbrot Set 图像上的位置的 X 或 Y 值,并在计算相应的 Julia 集时返回要迭代的函数:
const makeJuliaXStepFn = mandelXCoord => (x, y) => mandelXCoord + diffOfSquares(x, y)
const makeJuliaYStepFn = mandelYCoord => (x, y) => mandelYCoord + (2 * x * y)
Run Code Online (Sandbox Code Playgroud)
这些函数在以下逻辑中被调用:
mousemove事件的 Mandelbrot 集的图像上将鼠标指针的当前位置转换到 Mandelbrot 集的坐标空间,并将 (X,Y) 坐标传递给函数juliaCalcJS以计算相应的 Julia 集。
创建任何特定的 Julia Set 时,会调用上述两个偏函数来生成创建 Julia Set 时要迭代的函数
然后嵌套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
}
Run Code Online (Sandbox Code Playgroud)如您所见,通过调用makeJuliaXStepFn和循环makeJuliaYStepFn外返回的函数for被传递给juliaIter它,然后完成计算当前像素颜色的所有艰苦工作
当我看到这种代码结构时,起初我想“这很好,一切都很好;所以这里没有错”
除了有。性能比预期的要慢得多......
随之而来的是许多挠头和摆弄……
一段时间后,我发现,如果我移动的函数的创建juliaXStepFn和juliaYStepFn内部或者外部或内部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
}
Run Code Online (Sandbox Code Playgroud)
我原以为这种看似微不足道的更改会降低效率,因为每次迭代for循环时都会重新创建一对不需要更改的函数。然而,通过在for循环内移动函数声明,这段代码的执行速度提高了 2 到 3 倍!
谁能解释这种行为?
谢谢