记一次向 WebAssembly 的移植:gl-matrix

2019-06-23 20:19:52 +08:00
 dtysky

项目地址,欢迎 Star (如果你觉得还行):gl-matrix-wasm

由于某些工作上的原因,以及个人兴趣,配合之前用 Rust+WASM 写软件渲染器的踩坑,端午花了三天将gl-matrix移植到了 WASM (但因为各种奇奇怪怪的事情一直没时间发文)。此移植包含 gl-matrix 的所有功能,同时具备完整的单元测试。库本身以 Rust + wasm-bindgen + wasm-pack + webpack4 的形式开发,使用 TS + Karma + Mocha 来写单元测试(当然前两年还能扯一扯“优雅”啥的,现在这些都常识了也没啥说的)。在使用方面,我提供了两种使用模式,来应对不同的场合。同时还使用了很多 Trick 来在不破坏工程性的同时优化性能,某种意义上可以当做 Rust 来写 WASM 的工程的模板。

当然,新技术(或许也不算新了)总是看似很美好,坑却也无限大。具体详细的经历我就不说了(也没空),下面就大概说说一些坑,以及个人认为的 WASM 的优缺点吧。

坑(缺点)

坑其实和 WASM 本身关系不是特别大,主要是工具本身的坑,而有的缺点则是 WASM 目前切实的缺点了:

  1. 功能限制。如果你以为有了 Rust 就可以使用 Rust 所有能力快速开发一个 WASM 项目就太天真了,如果你的项目完全使用 Rust 编写或许可以,但一旦要和 JS 端进行交互,那就只能使用 Rust 的一个不大的子集(没有生命周期,没有 trait 等等...),函数无法返回引用(全走裸指针然后unsafe吧 www 要么就全走移动(其实是拷贝))。
  2. 内存管理。没有 GC,需要手动内存管理。其实这确实也不是什么坑,但对于大多数被 JS 惯坏的前端也确实是个问题。而且如果你完全使用 wasm-bindgen 来托管内存管理(虽然还是要在 js 端xx.free()释放),那么你对内存的掌握又会下降,这可能会造成一些性能坑(比如在本库中,由于完全托管给 Rust 创建结构体,比自己在 JS 端直接操作WebAssembly.Memory实例进行ArrayBuffer的操作要慢的多,当然这个还有#4 的问题)。
  3. 灵活性。这个其实是 Rust 的问题,其实仍然不算问题,只是 JS 这边确实有时候有的场景过于灵活,而 Rust 的借用规则由比较严格。举个例子,gl-matrix 中经常会有vec3.add(a, a, b)这种使用方式,而这个是无法通过 Rust 的借用规则的,但它编译到 WASM 后又没办法在编译期检查,咋整呢——它搞了个运行时检查,这不但会使得逻辑无法实现还会使得性能有所下降(除此之外还有空指针检查)。当然这正是 Rust 可靠的表现,所以我选择把它黑掉(划掉)。
  4. 工程灵活性。这个主要是 Rust 工具链的可定制性问题。目前这套流程可以生成 wasm + js + ts 头的组合,在够用的情况下很完美。但...如果要做一些魔改(比如修改生成的 JS 来优化性能或自定义功能),就比较难受了,我这边采用的是超级 Hack 的方法,详见wasm-opt.js的黑魔法。
  5. 互操作开销。Rust 本身通过 ABI 和 JS 互调用,但这个开销其实还蛮高的,这个可能会在某些状况打消掉 WASM 本身计算性能的优势。
  6. 异步。不错,目前 WASM 本身编译是强行异步的,这个可能会对代码组织有些坑(主要是库),如果是工程代码使用 Webpack 和异步模块其实也不是啥问题。
  7. 性能。这个其实不是 WASM 的问题,而是 Rust 工具链的问题(应该是),导致同样代码编译出来比 CPP + EMCC 编译出来慢两倍左右,具体见Issue 1585。这个问题我还没查明到底是啥(没空),如果有大佬帮忙再感谢不过。
  8. 体积。不错你没看错,同等功能 WASM 的体积比 JS 大两倍左右(至少在这个纯粹的库)。

优点

当然 WASM 本身优点也是有很多的(要不要它干嘛):

  1. 可控。在 WASM 中,内存对于开发者是完全透明的,它就是一块线性的 ArrayBuffer。Rust 在其之上帮我们抽象出了堆和栈,帮我们做了一些事情,但其本质还是非常透明的,而精确的内存控制对于游戏这种复杂应用的好处是巨大的。
  2. 可预测。在 WASM 中,性能是可以预测的。你不用担心 GC ( The world !)或是 JIT 带来的迷失感。同一个函数执行 100 次和 1000 次稳稳差 10 倍左右。
  3. 低开销。GPU 和内存开销都比 JS 更低,这个不用多说。
  4. 性能好。根据不同业务属性,性能提升不太一样。但是纯计算性能毋庸置疑是很高的,当然 JIT 后的 JS 也不差,这个下面会详细说。
  5. 深入融合 Webpack 工作流。这算是一个工程上可用的进步吧(比以前),Webpack4 中 WASM 已经是一等公民了。
  6. 不用再写 JS 啦!对部分开发者这个可能是最重要的(当然有 TS 这个属性可能弱了点)。
  7. 完整。这个是说 Rust 这套流程的,它的web_sysjs_sys实现了 WebIDL 的完整 Port,可用性还是很不错的(如果你不在意性能的话)。
  8. 还有的请补充......

性能

性能方面前面也说了,WASM 的运算性能是可预测的,而 JS 则由于 JIT 和 GC 会比较难以预测。对于本应用,由于 JS 版本使用了TypedArrayBuffer,以及进行的都是非常容易优化的简单纯数学运算,所以 JIT 后的 JS 版本性能可以说达到了 JS 可达到的上限。但即使在这种状况下,对于大多数的测试 WASM 版本还是要稳稳压1.5 倍左右:

详见Benchmark(Matrix4, 2015 RMBP, Chrome)

而对于真实世界,性能测试就没有 Benchmark 这么简单了,我写了 DEMO 来进行这个测试(当然对于 Web 1000 个物体的场景已经很大了):http://gl-matrix-wasm.dtysky.moe/

可见其实 WASM 版本在真实场景中还是有优势的(当然,Safari 除外,其 WASM 现在优化得还不行)。

当然,除了这个库本身我也测试过别的应用,比如 CRC32、数字图像处理、DOM 操作等等,大致结论就是在无法简单优化的密集计算场景下,WASM 有显著优势(比如模型压缩解压缩,加密解密),而在其他场合目前看来就差了点意思,投入产出比较低。

未来

未来来看,我觉得 WASM 有几点很值得关注:

  1. WASI。WebAssembly System Interface,不用多说,这说明我们以前对 WASM 本身都有很深的误解,它并不是为 Web 而生的汇编语言,而是个恰好可以跑在 Web 上的通用虚拟机。这是个好东西,对未来 WASM 在更多场景可用而言很重要。
  2. SIMD。simd.js提案被废弃后,SIMD on WebAssembly就成了 Web 使用 SIMD 唯一可预计的方式了,而 SIMD 可以为图形应用带来的好处也不用多说。
  3. 多线程。相比于阉割了SharedArrayBufferWorker(虽然有Transferable对象),这东西在某些场合很重要。

结论

一个项目是否要选择 WASM,还是要从其适用性出发。如果你的项目是对计算要求较高,而且没有频繁的 WASM <-> JS 互操作,同时能保证内存都是申请在 WASM 虚拟机中最好,那么你的项目是比较适合使用 WASM 的。否则至少在目前时点不太建议。

特别要求性能,目前暂时考虑 CPP 版本,Rust 版本在工程化的角度做的很好,而且团队足够重视,应该是未来的主流。

当然 WASM 本身目前起步也不是很久,它也在不断变好,相信未来它的能力和性能都会更强,也能适用于更多的领域(虽然大多领域应该还是干不过 JS,“我为啥要功夫学这玩意,JS 两下撸完回家打游戏不好吗?”)。

踩坑或许还不是特别深入,大佬觉得有问题或者建议请指正。

招人

本来想发招人的但目前也比较难,不过你有 P7 的实例肯定是没问题的,我们是支付宝互动图形团队,致力于在这个前端迷失的时代,在前端老本行的方向走的更远,给用户带来更好的体验。我们有自研 Web3D/2D 游戏引擎、巨量用户和业务场景,还和小程序团队直接相邻,有想法请直接联系我。

完。

3286 次点击
所在节点    前端开发
3 条回复
huiyadanli
2019-06-24 15:45:59 +08:00
好东西,但是没人看。。。
dtysky
2019-06-24 18:06:20 +08:00
@huiyadanli 因为发布的节点没上首页……
16fat
2020-03-10 10:52:06 +08:00
WebAssembly 不知道什么时候能支持 GC/DOM Integration

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/576690

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX