7

我目前正在开发一个图像编辑器,偶然发现了 V8 中关于像素操作和/或函数调用的这种奇怪行为。

http://jsperf.com/canvas-pixelwise-manipulation-performance

有两个测试用例。两个测试用例都应该操作内存中画布的图像数据以增加亮度。所以他们必须遍历每个像素并操作每个像素的 4 个颜色值。

情况1

案例 1 执行“总共 1 个函数调用”,这意味着它将上下文和 imageData 传递给一个函数,该函数然后迭代像素并操作数据。多合一功能

案例2

案例 2 执行“每个像素1 个函数调用”,这意味着它遍历像素并为每个像素调用一个方法,然后操作给定像素的 imageData。这导致(在这种情况下)250000 个额外的函数调用。

我的期望

我希望案例 1 比案例 2 快得多,因为案例 2 正在执行 250000 个额外的函数调用。

结果

在 Chrome 中,情况正好相反。如果我进行 250000 次额外的函数调用,它比处理所有图像操作的单个函数调用要快。

我的问题:为什么?

4

2 回答 2

3

两种代码都没有操作任何画布,并且在基准循环中定义函数并没有真正意义。你想要的是永远不会重新创建的静态函数,这样一旦 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将函数的原始文本大小(包括注释)视为内联决策的启发式方法。

于 2013-06-28T13:01:15.700 回答
1

我对 V8 的优化魔法不太熟悉,但我想说案例 2 为 V8 引擎重写代码留下了更多空间。
虽然乍一看,案例 1应该表现更好,但它并没有给 V8 留下太多发挥其魔力的空间。
虽然只有 1 个函数,但创建了一个调用对象,在该函数对象的范围内,声明了几个变量并正在处理一个巨大的对象。
但是,第二种情况可能只是转换为循环,甚至是字节移位,从而消除了对函数对象和作用域的需要。
除了省略范围/函数之外,您的变量(参数)也不需要复制,因此不会留下讨厌的对象引用来导致任何开销。

除了被复制的变量和引用之外,还需要考虑范围扫描:Math.abs从函数内调用(稍微)比在全局范围内慢。我不知道这是否属实,但我有这种偷偷摸摸的怀疑,即在更高范围内声明的屏蔽变量也可能会影响性能。
您还在one-function-approachwidth中使用and height,在我看来它们好像是隐含的全局变量。这会导致对循环的每次迭代进行范围扫描,这可能会导致比那些参数和调用更多的阻力......Math.*

于 2013-06-27T13:08:32.903 回答