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