更新(2020年3月2日)
事实证明,在我的示例中,编码是以正确的方式构造的,可以在V8 JavaScript引擎中从已知的性能悬崖上掉下来……
有关详细信息,请参阅关于bugs.chromium.org的讨论。这个错误现在正在研究中,应该在不久的将来修复。
更新(2020年1月9日)
我试图将以下面描述的方式运行的编码隔离到一个页面Web应用程序中,但是这样做,这种行为就消失了(??)。然而,下文所述的行为在全面适用的情况下仍然存在。
尽管如此,我已经优化了分形计算编码,这个问题在实时版本中不再是一个问题。如果有人感兴趣,那么显示此问题的JavaScript模块仍然可用这里。
概述
我刚刚完成了一个基于网络的小应用程序,比较了基于浏览器的JavaScript和Web的性能。此应用程序计算Mandelbrot集图像,然后当鼠标指针移动到该图像上时,动态计算相应的Julia集,并显示计算时间。
您可以在使用JavaScript (按'j')或WebAssembly (按'w')来执行计算和比较运行时之间切换。
单击这里查看工作应用程序
然而,在编写这段代码时,我发现了一些出乎意料的奇怪的JavaScript性能行为。
问题摘要
function
语法而不是新的ES6箭头语法重写所有函数。不幸的是,这并没有造成任何明显的差别。性能问题似乎与创建JavaScript函数的范围有关。在这个应用程序中,我调用了两个部分函数,每个函数都给出了另一个函数。然后,我将这些生成的函数作为参数传递给嵌套for
循环中调用的另一个函数。
相对于它执行的函数,for
循环似乎创建了类似于它自己的作用域的东西(但不确定它是一个完整的作用域)。然后,将生成的函数传递到这个作用域(?)边界太贵了。
基本编码结构
每个部分函数接收鼠标指针在Mandelbrot集图像上的位置的X或Y值,并返回在计算相应的Julia集时要迭代的函数:
const makeJuliaXStepFn = mandelXCoord => (x, y) => mandelXCoord + diffOfSquares(x, y)
const makeJuliaYStepFn = mandelYCoord => (x, y) => mandelYCoord + (2 * x * y)
这些函数在以下逻辑中调用:
mousemove
事件。juliaCalcJS
,计算相应的朱莉娅集。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 -将图像数组写入画布}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
}
我本以为这个看似微不足道的更改会降低效率,因为每次迭代for
循环时,都会重新创建一对不需要更改的函数。然而,通过在for
循环中移动函数声明,这段代码的执行速度要快2到3倍!
有人能解释一下这种行为吗?
谢谢
发布于 2020-03-02 07:06:11
我的代码在V8 JavaScript引擎中掉下了一个已知的性能悬崖.
问题和修复的细节在bugs.chromium.org上进行了描述。
https://stackoverflow.com/questions/59616335
复制