我目前正在制作一个图像编辑器,并在V8中偶然发现了关于像素操作和/或函数调用的奇怪行为。
http://jsperf.com/canvas-pixelwise-manipulation-performance
有两个测试用例。两个测试用例都应该操作内存中画布的图像数据以增加亮度。因此,他们必须迭代每个像素并操纵每个像素的4个颜色值。
案例1执行“总共1个函数调用”,这意味着它将上下文和imageData传递给函数,然后迭代像素并操纵数据。一体化功能
案例2执行“每个像素的1个函数调用 ”,这意味着它迭代像素并为每个像素调用一个方法,然后操纵给定像素的imageData。这导致(在这种情况下)250000个额外的函数调用。
我希望案例1比案例2快得多,因为案例2正在进行250000次额外的函数调用。
在Chrome中,它恰恰相反。如果我执行250000个额外的函数调用,它比处理所有图像操作的单个函数调用更快。
答案 0 :(得分:3)
两个代码都没有操纵任何画布,并且在基准测试循环中定义一个函数并没有多大意义。 你想要的是永远不会重新创建的静态函数,这样一旦JIT对它们进行了优化,它们就会保持优化。你没有 想要衡量函数开销的创建,因为真正的应用程序只会定义一次函数。
修复基准代码后,它们应该以相同的速度运行,因为manipulatePixel
函数会得到
内联。
http://jsperf.com/canvas-pixelwise-manipulation-performance/4
我还创建了另一个jsperf,我故意操纵V8启发式*而不是内联manipulatePixel
功能:
http://jsperf.com/canvas-pixelwise-manipulation-performance/5
正如您所看到的,它现在慢了50%。 2 jsperfs之间的唯一区别是manipulatePixel
函数中的巨大注释。
* V8将raw textual size of the function(包括评论)视为内联决策的启发式。
答案 1 :(得分:1)
我不太熟悉V8的优化模板,但我会说案例2为V8引擎留下了更多空间来重写代码。
虽然乍一看,案例1 应该表现得更好,但它并没有给V8留下太多空间来发挥其魔力。
虽然只有一个函数,但是在该函数对象的作用域内创建了一个调用对象,声明了几个变量并且正在处理一个巨大的对象。
但是,第二种情况可能只是转换为循环,甚至是字节移位,从而消除了对函数对象和范围的需求。
除了省略的范围/函数之外,不需要复制变量(参数),因此没有任何讨厌的对象引用会导致任何开销。
除了要复制的变量和引用之外,还需要考虑范围扫描:从函数内调用的Math.abs
(略微)慢于全局范围。我不知道这是否属实,但我有一种狡猾的怀疑,即在较高范围内声明的屏蔽变量也可能影响性能。
您还在 one-function-approach 中使用了width
和height
,它们看起来就好像它们是隐含的全局变量。这会导致对循环的每次迭代进行范围扫描,这可能会导致比这些参数和Math.*
调用更多的拖动...