最近在做一个项目需要在浏览器里做大量的计算。用JavaScript写的话性能不够用户体验很差。后来我用了WebAssembly把性能提升了好几倍。
WebAssembly这几年发展很快。从最早的实验性技术到现在已经成为了Web的标准。很多大型应用比如Figma AutoCAD Web版都在用WebAssembly来提升性能。
这篇文章我想分享一下WebAssembly的实战经验。从基础概念到具体的使用方法再到性能优化和踩坑经验希望能帮到想学习WebAssembly的朋友。
什么是WebAssembly
先简单介绍一下什么是WebAssembly。
WebAssembly简称WASM是一种低级的二进制指令格式。它可以在浏览器里运行而且性能接近原生。你可以用C C++ Rust等语言编写代码然后编译成WebAssembly在浏览器里运行。
WebAssembly的出现解决了JavaScript性能不足的问题。以前浏览器里只能跑JavaScript。JavaScript是解释型语言虽然有JIT优化但性能还是有上限。对于一些计算密集型的任务比如视频编辑3D渲染科学计算等JavaScript的性能不够用。
WebAssembly就不一样了。它是编译型的运行速度很快。而且它是二进制格式文件体积小加载快。现在主流的浏览器都支持WebAssembly。
WebAssembly不是要取代JavaScript而是和JavaScript互补。JavaScript负责DOM操作和交互逻辑WebAssembly负责计算密集型的任务。两者配合可以做出性能很好的Web应用。
WebAssembly能做什么
WebAssembly的应用场景很广泛。
第一个是游戏。很多大型游戏都移植到了Web上。比如Unity和Unreal引擎都支持导出WebAssembly版本。这样用户不用下载安装直接在浏览器里就能玩大型3D游戏。
第二个是视频和图像处理。视频编码解码图片处理滤镜等都是计算密集型的任务。用WebAssembly来做性能比JavaScript好很多。
第三个是CAD和设计工具。比如Figma就是用WebAssembly来做渲染引擎的。AutoCAD也有Web版用的也是WebAssembly。
第四个是科学计算。在浏览器里做数据分析模拟计算等。WebAssembly的高性能让这些任务在浏览器里运行成为可能。
第五个是加密和安全。一些加密算法用WebAssembly来实现性能更好。
第六个是移植现有代码。很多用C C++写的库可以直接编译成WebAssembly在浏览器里用。不用重新用JavaScript写一遍节省了大量的开发时间。
开发环境搭建
要开发WebAssembly首先要搭建开发环境。
如果你用C/C++的话需要安装Emscripten。Emscripten是一个编译器工具链可以把C/C++代码编译成WebAssembly。
安装Emscripten很简单。先从GitHub上克隆emsdk然后运行安装脚本。安装完成之后激活环境变量就可以用了。
如果你用Rust的话需要安装wasm-pack。wasm-pack可以把Rust代码编译成WebAssembly还能生成JavaScript的绑定代码。用起来很方便。
我个人比较推荐用Rust来写WebAssembly。Rust的内存安全性能好而且工具链很完善。当然如果你已经熟悉C/C++用Emscripten也可以。
一个简单的例子
来看一个简单的例子。用Rust写一个斐波那契数列计算函数然后编译成WebAssembly在浏览器里调用。
首先创建一个Rust库项目。在Cargo.toml里添加wasm-bindgen依赖。wasm-bindgen可以让JavaScript和WebAssembly之间互相调用。
然后在src/lib.rs里写一个斐波那契函数。用#[wasm_bindgen]标记这个函数这样JavaScript就能调用它了。
写完之后用wasm-pack build命令编译。编译完成之后会生成一个pkg目录里面有WebAssembly文件和JavaScript的绑定代码。
然后在HTML里引入生成的JavaScript文件。调用fib函数就可以了。
整个过程很简单。几十行代码就能跑起来一个WebAssembly的例子。
性能对比
来看看WebAssembly和JavaScript的性能对比。
我做了一个测试。计算斐波那契数列的第40项。用JavaScript写的版本和用WebAssembly写的版本分别运行100次取平均时间。
JavaScript版本用了大概120毫秒。WebAssembly版本用了大概35毫秒。性能提升了3倍多。
这还只是一个简单的例子。对于更复杂的计算比如矩阵运算图像处理等WebAssembly的性能优势更明显。有时候能达到5到10倍的提升。
当然WebAssembly也不是银弹。它也有开销。比如WebAssembly和JavaScript之间的调用有开销。如果频繁地在两者之间传数据性能反而会下降。所以要尽量减少跨语言的调用把计算密集型的代码都放在WebAssembly里。
内存管理
WebAssembly有自己的内存空间。和JavaScript的内存是分开的。
WebAssembly的内存是一个线性的字节数组。你可以在里面读写数据。JavaScript可以通过TypedArray来访问WebAssembly的内存。
在C/C++里你可以用malloc和free来管理内存。在Rust里内存是自动管理的。但要注意WebAssembly的内存不会自动增长。如果需要更多内存要手动调用grow函数。
传数据的时候要注意。如果传字符串或者复杂对象需要先把数据写到WebAssembly的内存里然后传一个指针过去。WebAssembly那边再从指针读取数据。这个过程有一些开销所以要尽量减少数据传输。
wasm-bindgen已经帮我们处理了很多这些细节。它可以自动在JavaScript和WebAssembly之间转换字符串和对象。用起来很方便。
性能优化
使用WebAssembly的时候有一些性能优化的技巧。
第一个是尽量减少JavaScript和WebAssembly之间的调用。每次调用都有开销。如果需要频繁调用可以把逻辑都放到WebAssembly里一次性调用完成。
第二个是减少数据拷贝。在JavaScript和WebAssembly之间传数据会有拷贝开销。可以直接在WebAssembly的内存里操作数据避免拷贝。
第三个是使用SIMD。WebAssembly支持SIMD指令可以并行处理多个数据。对于图像处理矩阵运算等场景SIMD能大幅提升性能。
第四个是优化编译。发布的时候用release模式编译。开启优化选项比如O3。这样生成的WebAssembly代码更小更快。
第五个是流式编译。现代浏览器支持流式编译WebAssembly。就是一边下载一边编译。这样加载速度更快。要确保服务器发送正确的Content-Type头。
第六个是使用Web Worker。WebAssembly可以在Web Worker里运行。这样计算不会阻塞主线程用户界面保持流畅。对于长时间运行的计算任务一定要用Web Worker。
踩过的坑
在使用WebAssembly的过程中我也踩过一些坑。
第一个坑是调试困难。WebAssembly是二进制的调试起来不像JavaScript那么方便。虽然现在浏览器支持source map可以调试但还是不如JavaScript方便。建议在开发的时候先在原生环境调试好再编译成WebAssembly。
第二个坑是文件体积。WebAssembly文件有时候会很大。特别是用C++写的带了很多标准库的代码。要注意优化体积。可以用strip去掉调试信息用Oz优化体积。还可以用gzip或brotli压缩。
第三个坑是兼容性。虽然现在主流浏览器都支持WebAssembly但一些老版本的浏览器不支持。如果需要兼容老浏览器要做降级处理。可以检测浏览器是否支持WebAssembly如果不支持就用JavaScript的降级版本。
第四个坑是异步加载。WebAssembly是异步加载的。不能在页面加载完成后立刻调用。要等WebAssembly加载和编译完成之后才能调用。可以用async/await来处理。
第五个坑是浮点数精度。WebAssembly的浮点数运算和JavaScript的浮点数运算可能有细微的差别。如果对精度要求很高要注意测试。
实际项目中的应用
说说我在实际项目中是怎么用WebAssembly的。
我做的是一个在线图片处理工具。用户上传图片之后可以做滤镜裁剪调色等操作。这些操作如果用JavaScript来做速度很慢特别是处理大图的时候用户要等很久。
后来我把图片处理的核心算法用Rust重写了然后编译成WebAssembly。性能提升了5倍多。用户处理大图的时候几乎是实时的体验好了很多。
具体的做法是把图片数据传到WebAssembly的内存里然后调用处理函数处理完之后再从内存里读出来。整个过程都在Web Worker里运行不会阻塞UI。
用了WebAssembly之后用户反馈很好。很多人说这个工具比其他在线图片处理工具快很多。
未来的发展
WebAssembly还在不断发展。未来会有更多的特性。
比如WebAssembly的线程支持。现在已经有了SharedArrayBuffer和Atomics可以做多线程编程。未来会有更完善的线程支持。
比如WebAssembly的GC支持。这样就可以直接在WebAssembly里操作JavaScript的对象不用手动管理内存。
比如WebAssembly的组件模型。可以让不同语言写的WebAssembly模块互相调用。
这些特性都会让WebAssembly变得更强大更易用。
写在最后
WebAssembly是一项很有前景的技术。它让浏览器能运行高性能的代码拓展了Web应用的边界。
如果你需要在浏览器里做计算密集型的任务或者想把现有的C/C++/Rust代码移植到Web上WebAssembly是一个很好的选择。
当然WebAssembly不是万能的。它也有学习成本和使用限制。要根据实际的需求来选择是否使用。
希望这篇文章的分享能帮到想学习WebAssembly的朋友。也欢迎大家在评论区交流分享你们的经验。
最后用一句话来结束这篇文章:WebAssembly让Web的未来有了无限可能。
愿每一个前端开发者都能掌握WebAssembly做出更强大的Web应用。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录