为什么asm.js会降低性能

Why does asm.js deteriorate performance?

本文关键字:性能 asm js 为什么      更新时间:2023-09-26

只是为了看看它是如何执行的,我手工编写了一个非常短的asm.js模块,它使用32位整数数学和类型化数组(Int32Array)模拟2D波动方程。我有三个版本,都尽可能地相似:

  1. 普通(即易读,尽管是c风格)JavaScript
  2. 与1相同,添加了asm.js注释,以便根据Firefox和其他工具通过验证器
  3. 与2相同,只是没有"use asm";指令在顶部

我在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 (version 44),以毫秒为单位每次迭代:

  • FIREFOX (version 41): 20 ms, 35 ms, 60 ms.
  • CHROME(版本44):25毫秒,150毫秒,75毫秒

所以普通的JavaScript在两个浏览器中都占上风。asm.js所需注解的存在会使两者的性能下降3倍。此外,"使用asm"的存在;指令有明显的效果——它帮助了Firefox一点,并使Chrome屈服!

似乎很奇怪,仅仅添加位操作符就会带来三倍的性能下降,这是不能通过告诉浏览器使用asm.js来克服的。另外,为什么让浏览器使用asm.js只在Firefox中有些许帮助,而在Chrome中却完全适得其反呢?

实际上asm.js并不是为了手工编写代码而创建的,而是从其他语言编译的结果。据我所知,没有工具可以验证asm.js代码。您是否尝试过用C语言编写代码并使用Emscripten来生成asm.js代码?我强烈地怀疑结果将是完全不同的,并针对asm.js进行了优化。

我认为混合类型化和非类型化的变量只会增加复杂性而没有任何好处。相反,"asm.js"代码更复杂:我试图解析asm.js和jointjs.com/demos/javascript-ast上的普通函数,结果是:

  • 纯js函数有137个节点和746个令牌
  • asm.js函数有235个节点和1252个token

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

切换asm.js上下文有一些固定成本。理想情况下,你只需要做一次,然后在应用程序中以asm.js的形式运行所有代码。然后,您可以使用类型化数组控制内存管理,并避免大量垃圾收集。我建议重写分析器并在asm.js中度量asm.js——不需要上下文切换。