随着 float32 优化,asm.js 与原生性能之间的差距进一步缩小

asm.js 是 JavaScript 的一个简单子集,非常容易优化,适合作为来自 C 和 C++ 等语言的编译器目标。今年早些时候,Firefox 可以以大约原生速度的一半运行 asm.js 代码 - 也就是说,由 emscripten 生成的 C++ 代码可以以大约原生编译的相同 C++ 代码速度的一半运行 - 我们认为通过改进 emscripten(从 C++ 生成 asm.js 代码)和 JS 引擎(运行 asm.js 代码),可以更接近原生速度。

从那时起,许多加速已经出现,其中很多是小型和特定的,但也有一些大型功能。例如,Firefox 最近获得了优化某些浮点运算的能力,使其能够使用 32 位浮点数进行运算,而不是 64 位双精度数,在某些情况下可以提供显著的加速,如该链接所示。这种优化工作是通用的,适用于任何碰巧以这种方式可优化的 JavaScript 代码。在完成这项工作并实现加速之后,没有理由不将 float32 添加到 asm.js 类型系统中,以便 asm.js 代码可以专门从中受益。

在 emscripten 和 SpiderMonkey 中实现这一点的工作最近已经完成,以下是性能数据

asm1.5b

运行时间归一化为 clang,因此越低越好。红色条(firefox-f32)代表 Firefox 在使用 float32 的 emscripten 生成的代码上运行。如图表所示,具有 float32 优化的 Firefox 可以以比原生速度快 1.5 倍或更快的速度运行所有这些基准测试。这是对今年早些时候的重大改进,当时正如之前提到的,事情更接近原生速度的两倍慢。您还可以通过将其与旁边的橙色条(firefox)进行比较,来查看由于 float32 优化带来的具体改进 - 在诸如蒙皮、linpack 和 box2d 这样的浮点密集型基准测试中,加速非常明显。

关于这些数字,还需要注意的一点是,不仅显示了一个原生编译器,而是两个,clang 和 gcc。在一些基准测试中,clang 和 gcc 之间的差异很大,这表明虽然我们经常谈论“比原生速度慢多少倍”,但“原生速度”是一个有点宽泛的术语,因为原生编译器之间存在差异。

事实上,在一些基准测试中,比如 box2d、fasta 和 copy,asm.js 与 clang 一样接近,甚至比 clang 更接近 gcc。甚至有一个例子,asm.js 在 box2d 上略微超过了 clang(gcc 也在该基准测试中超过了 clang,幅度更大,因此 clang 的后端代码生成可能只是碰巧有点不幸)。

总的来说,这表明“原生速度”不是一个单一数字,而是一个范围。看起来 Firefox 上的 asm.js 非常接近该范围 - 也就是说,虽然它平均比 clang 和 gcc 慢,但它慢的程度与原生编译器之间的差异并不远。

请注意,emscripten 中默认情况下关闭了 float32 代码生成。这是故意的,因为它既可以提高性能,又可以确保正确的 C++ 浮点数语义,但它也会增加代码大小 - 由于添加了 Math.fround 调用 - 这在某些情况下可能是有害的,尤其是在尚未支持 Math.fround 的 JavaScript 引擎中。

有一些方法可以解决这个问题,例如轮廓选项,它可以减少最大函数大小。我们还有一些其他关于如何在 emscripten 中改进代码生成的想法,因此我们将继续尝试这些方法,以及关注 Math.fround 在浏览器中的支持情况(目前 Firefox 和 Safari 支持)。希望在不久的将来,我们可以默认在 emscripten 中启用 float32 优化。

总结

总之,上面的图表显示了 asm.js 性能越来越接近原生速度。虽然由于上述原因,我不建议人们现在就使用 float32 优化进行构建 - 希望很快就能实现!- 但这是一个令人兴奋的性能提升。甚至目前的性能数据 - 比原生速度快 1.5 倍或更快的速度 - 也不是可以达到的极限,因为无论是在 emscripten 还是在 JavaScript 引擎中,都还有很多重大改进正在进行或正在计划中。

关于 Alon Zakai

Alon 是 Mozilla 研究团队的一员,他在那里主要负责 Emscripten,这是一个将 C 和 C++ 编译为 JavaScript 的编译器。Alon 于 2010 年创立了 Emscripten 项目。

更多由 Alon Zakai 撰写的文章…

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道师和编辑。进行演讲和博客文章,主题包括 HTML5、JavaScript 和开放网络。Robert 是 HTML5 和开放网络的坚定支持者,从 1999 年开始从事 Web 前端开发工作 - 在瑞典和纽约市。他还在http://robertnyman.com 定期撰写博客,喜欢旅行和结识新朋友。

更多由 Robert Nyman [荣誉编辑] 撰写的文章…


20 条评论

  1. Hervé Renault

    这是一个好消息。我在 http://mozillazine-fr.org/lecart-se-reduit-entre-c-et-javascript/ 上发表了一篇总结文章。
    希望 2014 年能从 Mozilla 看到更多像这样的好消息。
    干杯!

    2013 年 12 月 20 日 下午 03:20

    1. Robert Nyman [编辑]

      谢谢!

      2013 年 12 月 20 日 上午 09:08

  2. Rimantas

    没有人关心浮点运算。让 DOM 操作像原生一样快和流畅,然后我们再谈。

    2013 年 12 月 20 日 上午 09:01

    1. Robert Nyman [编辑]

      我个人认为,人们非常关心获得更好的性能,如果这有助于实现这一目标,我认为这是一件好事。而且这并不是非此即彼的事情,有很多领域需要努力才能使事物变得更快更好。

      2013 年 12 月 20 日 上午 09:10

    2. Alon Zakai

      有些人确实关心,例如游戏和游戏引擎。DOM 性能当然也很重要,但不同的人负责这些方面,一个方面的改进并不妨碍另一个方面的改进。

      2013 年 12 月 20 日 下午 11:14

    3. Boris

      您正在做的事情与哪些特定的 DOM 操作有关?我们一直在努力使 DOM 变得更快,非常欢迎您提供具体且实际的测试用例,这些用例表明了速度慢的问题!

      2013 年 12 月 21 日 上午 00:09

      1. Glamsci

        通过设置 CSS(甚至不使用任何库,而是通过 element.style.width 等)执行的任何补间动画在 Firefox 中都显得非常卡顿,而在 Chrome 中则显得流畅。通常,使用 DOM 的视觉效果希望流畅地完成,而不是出现跳跃式剪切并让用户感到意外,因此这是一个大问题。

        jQuery、Tween.JS(顺便说一句,它并不是那么好 - 即使您想要 100 步,也可能只得到 7 步),即使我只创建一个轻量级的 setTimer 递归函数,该函数带有对当前值的回调,并先启动该函数,以便 JIT 获取它,使用大型 div 移动仍然会卡顿。

        因为如果我每秒设置 CSS 值的次数超过几次,Firefox 会在渲染它时出现问题,即使使用原生方法也是如此 - 它的调度在 DOM 方面可能很差,或者 DOM 操作效率低下。这使得在 Firefox 中以一致的方式流畅地更改形状或移动事物变得不可能。

        2013 年 12 月 21 日 下午 23:38

        1. 匿名

          同意。虽然在 Chrome 和 Firefox for Android 中,我发现情况恰恰相反。奇怪。

          2013 年 12 月 23 日 上午 09:58

        2. Hervé Renault

          虽然我没有看到 Firefox 和 Chrome 之间存在如此大的差距……也许 Firefox 会随着 Electrolysis 项目的进行而改进:https://wiki.mozilla.org/Electrolysis

          2013 年 12 月 23 日 上午 11:09

        3. Robert O’Callahan

          您愿意用具体的测试用例提交一个错误报告吗?设置 CSS “width” 的性能将很大程度上取决于具体的内容。感谢您!

          2013 年 12 月 27 日 下午 19:10

  3. kruger

    最好能包含 C++ 编译标志和机器规格。是否使用了 -O3、-march=native?因为 Firefox 在执行 JavaScript 时会使用所有可用的 CPU 指令,我假设是这样?
    另外,编译器也很旧。我的 Fedora 已经有了 clang 3.3 和 gcc 4.8。他们并没有停止进步。

    2013 年 12 月 20 日 上午 09:04

    1. Alon Zakai

      您可以通过运行emscripten 基准测试套件 来重现结果,并查看其中的编译标志。标志是 -O2,如果您在使用其他标志时得到不同的结果,请告诉我(在我的机器上似乎没有区别)。我的机器是 i7-2600 @ 3.40GHz。

      Clang 版本为 3.2,落后 1 个版本。Emscripten 使用了 3.2 版本,因此在原生构建中也使用它,这样比较起来就更公平了。GCC 版本是我使用的发行版(Ubuntu 12.04)安装的。没错,更新的版本可能会产生影响,但总体而言,这里的目标是与几个“合理的”原生编译器进行比较——例如,这个 GCC 版本是合理的,因为许多人使用发行版编译器进行构建——并大致了解原生编译器之间以及 asm.js 与这些编译器之间的差异。为此,我认为测试的编译器很好。但我同意,如果你想看到当前性能的绝对极限,或者准确地了解 GCC 或 Clang 主干的性能,那么使用其他编译器会更好,只是这里目标不同。

      2013 年 12 月 20 日 下午 11:12

  4. Axel Rauschmayer

    我理解使用 ECMAScript 6 中的 Math.fround() 来处理 float32 类型的标注(?)

    2013 年 12 月 20 日 下午 11:13

    1. Alon Zakai

      是的,使用了 fround。没有它,在 JS 中高效地获得 float32 语义非常困难。

      2013 年 12 月 20 日 下午 11:15

  5. Ron

    这里没有探讨的一个相关问题是,V8 和 Spidermonkey 在没有 asm.js 的情况下可以运行 JavaScript 多快。我估计,普通 JavaScript 与 asm.js 之间的差距正在缩小,就像 asm.js 与原生代码之间的差距正在缩小一样。

    2013 年 12 月 20 日 下午 12:44

    1. Dany

      有点离题了,你怎么能在不使用 asm.js 的情况下将 C++ 编译成 JavaScript?

      无论如何,使用像 asm.js 这样的子集可以做到的优化会极大地提高性能。
      使用手工调整的普通 JavaScript 与使用编译后的 asm.js 版本相比,会有很大的区别,可能是 10 倍甚至更多。

      2013 年 12 月 21 日 上午 04:29

  6. M. Edward Borasky (@znmeb)

    这是个好消息!大多数游戏需要计算密集型操作——图形和音频——可以在 32 位浮点数中正常工作。只有大型矩阵/科学计算需要 64 位。

    2013 年 12 月 20 日 下午 17:21

  7. DDT

    这不仅对游戏很有前景,而且对保护我们 JS 应用程序的加密函数也很有前景。但在加密中,你主要使用的是整数运算,而不是浮点运算。

    2013 年 12 月 23 日 上午 01:59

  8. Xavi Conde

    关于你的图表,

    a) 为什么你以 Clang 为基准进行归一化?看起来 GCC 通常比 Clang 快。

    b) Firefox 和 f-32 之间的性能差异并不大。

    c) 我很想知道你的测试在 Chrome 中的性能表现如何。

    d) 你在这里针对哪种用例进行了浮点优化?正如上面一些人指出的那样,这不是最常见的操作。大多数 JavaScript 应用程序不会处理大量的浮点运算。

    2013 年 12 月 24 日 上午 02:10

    1. Alon Zakai

      a) 在之前的比较中,我们对 Clang 进行了比较和归一化,因此归一化的数字处于一个熟悉的范围内。但没错,也许我们应该考虑更改它。

      b) 在大多数基准测试中是的,但在少数情况下,差异非常大,例如蒙皮。

      c) 你可以在此处查看 Chrome 的数字:http://arewefastyet.com/#machine=12&view=breakdown&suite=asmjs-apps

      d) 游戏一直是 Emscripten 和 asm.js 的主要用例之一,我想是因为 (1) 它们希望在网络上运行,这样就能接触到更多用户,以及 (2) 它们确实需要最大性能。而且游戏经常使用浮点运算,事实上,这里的一些从 float32 优化中获益的基准测试是来自游戏引擎的真实代码:子弹、Box2D 和蒙皮。

      2013 年 12 月 24 日 上午 09:34

本文评论已关闭。