为什么asm.js性能恶化?

为了看看它是如何执行的,我手写了一个非常短的asm.js模块,它使用32位整型math和types化数组(Int32Array)来模拟二维波动方程。 我有三个版本,尽可能相似:

  1. 普通的(即清晰的,尽pipe是C风格的)JavaScript
  2. 与1相同,添加了asm.js注释,以便通过validation器,根据Firefox和其他工具
  3. 除了没有“使用asm”之外,与2相同。 指令在顶部

我在http://jsfiddle.net/jtiscione/xj0x0qk3/上留下了一个演示,它允许您在模块之间切换以查看使用每个模块的效果。 所有三个工作,但速度不同。 这是热点(与asm.js注释):

for (i = 0; ~~i < ~~h; i = (1 + i)|0) { for (j = 0; ~~j < ~~w; j = (1 + j)|0) { if (~~i == 0) { index = (1 + index) | 0; continue; } if (~~(i + 1) == ~~h) { index = (1 + index) | 0; continue; } if (~~j == 0) { index = (1 + index) | 0; continue; } if (~~(j + 1) == ~~w) { index = (1 + index) | 0; continue; } uCen = signedHeap [((u0_offset + index) << 2) >> 2] | 0; uNorth = signedHeap[((u0_offset + index - w) << 2) >> 2] | 0; uSouth = signedHeap[((u0_offset + index + w) << 2) >> 2] | 0; uWest = signedHeap [((u0_offset + index - 1) << 2) >> 2] | 0; uEast = signedHeap [((u0_offset + index + 1) << 2) >> 2] | 0; uxx = (((uWest + uEast) >> 1) - uCen) | 0; uyy = (((uNorth + uSouth) >> 1) - uCen) | 0; vel = signedHeap[((vel_offset + index) << 2) >> 2] | 0; vel = vel + (uxx >> 1) | 0; vel = applyCap(vel) | 0; vel = vel + (uyy >> 1) | 0; vel = applyCap(vel) | 0; force = signedHeap[((force_offset + index) << 2) >> 2] | 0; signedHeap[((u1_offset + index) << 2) >> 2] = applyCap(((applyCap((uCen + vel) | 0) | 0) + force) | 0) | 0; force = force - (force >> forceDampingBitShift) | 0; signedHeap[((force_offset + index) << 2) >> 2] = force; vel = vel - (vel >> velocityDampingBitShift) | 0; signedHeap[((vel_offset + index) << 2) >> 2] = vel; index = (index + 1)|0; } } 

“普通JavaScript”版本的结构如上,但没有asm.js要求的按位运算符(例如“x | 0”,“~~ x”,“arr [(x << 2)>> 2]”,等等。)

这些是我机器上所有三个模块的结果,使用Firefox(Developer Edition v。41)和Chrome(版本44),每次迭代以毫秒为单位:

  • FIREFOX(版本41):20 ms,35 ms,60 ms。
  • CHROME(版本44):25 ms,150 ms,75 ms。

所以普通的JavaScript在这两个浏览器中都会胜出 asm.js需要注释的存在会使性能下降两倍。 此外,“使用asm”的存在; 指令有明显的效果 – 它有助于Firefox,并使Chrome一触即发!

看起来奇怪的是,仅仅添加按位运算符应该引起三倍的性能下降,这是无法通过告诉浏览器使用asm.js来克服的。 另外,为什么告诉浏览器使用asm.js只能在Firefox中稍微有所帮助,并在Chrome中完全适得其反?

实际上asm.js并没有被创build来手动编写代码,而只是作为其他语言编译的结果。 据我所知,没有validationasm.js代码的工具。 你有没有尝试用C lang编写代码,并使用Emscripten生成asm.js代码? 我强烈怀疑这个结果会有很大的不同,并为asm.js优化。

我认为混合型和非型型的变化只会增加复杂性而没有任何好处。 相反,“asm.js”代码更复杂:我尝试parsing了jointjs.com/demos/javascript-ast上的asm.js和plain函数,结果如下:

  • 普通的js函数有137个节点和746个令牌
  • asm.js函数有235个节点和1252个令牌

我会说,如果你有更多的指令在每个循环执行,它很容易会变慢。

切换asm.js上下文有一些固定成本。 理想情况下,您只需执行一次,然后将所有代码作为asm.js运行。 然后你可以使用types化数组来控制内存pipe理,并避免大量的垃圾回收。 我build议重写分析器,并测量asm.js中的asm.js – 没有上下文切换。