164 lines
17 KiB
Markdown
164 lines
17 KiB
Markdown
|
# 10 | 有哪些已经投入生产的 WebAssembly 真实案例?
|
|||
|
|
|||
|
你好,我是于航。
|
|||
|
|
|||
|
本节课,我们将不再“拘泥”于 Wasm 的实现细节,而是要从技术标准走向生产实践。作为应用篇中的第二节课,我们将一起来看看从 2017 年 Wasm MVP 标准的确定,直到如今 WASI 出现,使得 Wasm 走出 Web 的这几年时间里,现实世界中有哪些已经投入生产的 Wasm 真实案例?而这些案例又是怎样利用 Wasm,解决了哪方面实际问题的呢?(这节课里介绍的几个案例,均由我总结于网络上相关公司发布的文章或视频分享。)
|
|||
|
|
|||
|
## eBay - Barcode Scanner
|
|||
|
|
|||
|
第一个我们要介绍的实际案例来自于 eBay 在 Wasm 上的一次尝试。
|
|||
|
|
|||
|
eBay 是一家知名的线上拍卖与购物网站,人们可以通过 eBay 来在线出售自己的商品。作为一家知名的购物网站,为了优化用户录入待售商品的操作流程,eBay 在自家的 iOS 与 Android 原生应用中提供了“条形码扫描”功能。
|
|||
|
|
|||
|
通过这个功能,应用可以利用移动设备的摄像头扫描产品的 UPC 条形码,然后在后台数据库中查找是否有已经提交过的类似商品。若存在,则自动填写“商品录入清单”中与该物品相关的一些信息,从而简化用户流程,优化用户体验。
|
|||
|
|
|||
|
### 问题所在
|
|||
|
|
|||
|
在 iOS 与 Android 原生应用中,eBay 借助了自研的、使用 C++ 编写的条形码扫描库,来支持 UPC 条形码的扫描功能。而这对于诸如 iOS 与 Android 等 Native 平台来说,条形码的实际扫描性能得到了不错的保障,应用表现良好。
|
|||
|
|
|||
|
但是随着 eBay HTML5 应用的使用人数越来越多,为了能够使用户的商品录入流程与 Native 应用保持一致,“如何为 HTML5 应用添加高效的条形码扫描功能?”便成为了 eBay 工程师团队亟待解决的一个问题。
|
|||
|
|
|||
|
初期,技术团队使用了 GitHub 上的开源 JavaScript 版本条形码扫描器,来为 HTML5 应用支持 UPC 条形码的解析功能。但随着不断收到的用户反馈,团队发现 JavaScript 版本的条形码扫描器仅能够在 20% 的时间里表现良好,而在剩下 80% 的时间中,条形码的实际解析效率却不尽如人意,用户的每一次扫码过程都无法得到一致、流畅的用户体验。
|
|||
|
|
|||
|
出现这种问题的一个最为重要的原因,便是由于 JavaScript 引擎在实际优化代码执行的过程中,无法确保用户的每一次扫描过程都能够得到 JIT 的优化。JavaScript 引擎采用的“启发式”代码执行和优化策略,通常会首先通过 Profiling 来判断出“热代码”的具体执行路径,然后再调用 JIT 引擎来优化这段代码。而实际上,究竟哪段代码能够被优化,谁也无从得知。
|
|||
|
|
|||
|
### 可能的解决方案
|
|||
|
|
|||
|
那么,如何解决这个问题?其中的一个选择是等待 WICG(Web Incubator Community Group,Web 孵化社区群组)曾提出的 “Shape Detection API” 提案。这个提案提出了一系列的 API,可以让 Web 平台应用直接利用硬件加速或者系统相关的资源,来支持如人脸识别、条形码识别等功能。但该提案目前仍处于起步阶段,要实现跨浏览器的兼容性还有很多路要走。
|
|||
|
|
|||
|
eBay 技术团队所想到的另外一个方案,便是 Wasm。从下图所示的 V8 引擎编译管道中你可以看出。相较于 JavaScript 而言,浏览器引擎在执行 Wasm 字节码时不需要经过诸如“生成 AST”、“生成 Bytecode 字节码”、“生成 IR” 以及“收集运行时信息”等多个步骤。JavaScript 引擎的优化编译器后端可以直接将 Wasm 字节码转换为经过优化的机器码,进而以接近 Native 代码的效率来执行。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/27/57/272112ea462c9d8c426ce331cca7e957.png)
|
|||
|
|
|||
|
不仅如此,Wasm 字节码在实际的执行过程中,也不会存在类似 JavaScript 代码的“去优化”过程,因此性能表现会更加稳定。
|
|||
|
|
|||
|
另一方面,借助于 Wasm 相关编译工具链的帮助,eBay 技术团队可以直接使用曾经为 Native 平台设计开发的 C++ 条形码扫描库。总的来说,eBay 技术团队不需要为 Wasm 重新编写这部分功能,而仅需要对已有的代码库进行少量改动即可。
|
|||
|
|
|||
|
### 项目架构
|
|||
|
|
|||
|
当方案确定之后,条形码扫描功能的具体工作流程如下所示。
|
|||
|
|
|||
|
* 使用 Web Worker API 从主线程创建一个工作线程(Worker Thread),用于通过 JavaScript 胶水代码来加载和实例化 Wasm 模块;
|
|||
|
* 主线程将从摄像头获得到的视频流数据传递给工作线程,工作线程将会调用从 Wasm 模块实例中导出的特定函数,来处理这些视频流像素。函数在调用完成后,会返回识别出的 UPC 字符串或者返回空字符串,以表示没有检测到有效的条形码内容;
|
|||
|
* 应用在运行时会通过设置“阈值时间”的方式,来检测是否读取到有效的条形码信息。当扫描时间超过这个阈值时,应用会弹出提示信息以让用户重试,或选择手动输入二维码序列。当然,阈值超时可能意味着两种情况:一种是用户没有扫描到有效的条形码;第二种是读取到的二维码视频流无法被应用使用的算法正确解析。
|
|||
|
|
|||
|
项目中使用到的 Wasm 模块以及 JavaScript 胶水代码,均是通过 Emscripten 工具链编译已有的 C++ 条形码扫描库得来的。整个方案的工作流程如下图所示。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/92/f4/929021573b6a7b9e38e8937986af42f4.png)
|
|||
|
|
|||
|
### 一致化的编译管道
|
|||
|
|
|||
|
作为工程化的一部分,如何将 Wasm 模块的开发和编译流程,也一并整合到现有的 Web 前端项目开发流程中,是每个实际生产项目都需要考虑的事情。
|
|||
|
|
|||
|
一个 Wasm 模块,或者说是 Wasm Web 应用的完整开发流程涉及到多个部分。除了组成应用最基本的 HTML、CSS 以及 JavaScript 代码外,对于 Wasm 模块的开发和编译,我们还需使用到由 Rust 和 C++ 等系统级编程语言编写的模块源文件、相关的标准库,以及用于编译这些源代码的编译工具链,比如 Emscripten。
|
|||
|
|
|||
|
为了确保每次都能够在一个一致的环境中来编译和生成 Wasm 模块,同时简化整个项目中 Wasm 相关开发编译环境的部署流程。eBay 技术团队尝试采用了 Docker 来构建统一的 Wasm 编译管道。这样在每次编译 Wasm 模块时,Docker 都会启动一个具有相同环境的容器,来进行模块的编译流程,从而磨平了不同开发环境下可能带来的编译结果差异。
|
|||
|
|
|||
|
不仅如此,通过结合 NPM 下 “package.json” 文件中的自定义脚本命令,我们还可以让 Wasm 模块的开发与编译流程,与现有的 Web 前端应用开发编译流程,更加无缝地进行整合。举个例子,比如我们可以按照如下形式来组织 “package.json” 文件中的应用编译命令。
|
|||
|
|
|||
|
```
|
|||
|
{
|
|||
|
"name": "my-wasm-app",
|
|||
|
"scripts": {
|
|||
|
"build:emscripten": "docker run --rm -v $(pwd)/src:/src trzeci/emscripten ./build.sh",
|
|||
|
"build:app": "webpack .",
|
|||
|
"build": "npm run build:emscripten && npm run build:app",
|
|||
|
// ...
|
|||
|
},
|
|||
|
// ...
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
其中,命令 “build:emscripten” 主要用于启动一个带有完整 Emscripten 工具链开发环境的 Docker 容器。并且在容器启动后,通过执行脚本 “./build.sh” ,来编译当前目录下 “src” 文件夹内的源代码到对应的 Wasm 二进制模块。“build:app” 命令则用于编译原有 Web 应用的 JavaScript 代码。最后我们将两部分再进行整合,便得到了最终的 “build” 命令。
|
|||
|
|
|||
|
### 并不理想
|
|||
|
|
|||
|
以上基于 Wasm 的方案看起来十分理想。但经过实际测试后,eBay 技术团队发现,虽然基于 Wasm 的实现可以在 1 秒的时间内处理多达 50 帧的画面,但实际的识别成功率却只有 60%。剩下 40% 的失败情况大多是因为采样的画面角度不好,进而使得条形码的拍摄图像质量不高。产生问题的关键点,在于当前应用使用的是自研的 C++ 条形码扫描库。
|
|||
|
|
|||
|
自研的 C++ 条形码扫描库其一大特征为条形码的识别解析算法效率高,但仅适用于条形码成像质量较高的情况下。因此,急需一种方式来弥补在成像质量偏低时的条形码识别。
|
|||
|
|
|||
|
此时,团队将目光锁定到了另外一个业界十分有名的、基于 C 语言编写的开源条形码扫描库 —— ZBar。通过实验发现,当使用 ZBar 作为条形码扫描库时,在所设置的阈值时间范围内,整个应用的扫描成功率提高到了 80%。
|
|||
|
|
|||
|
但 80% 的成功率对于产品的用户体验来说仍然不够。团队继续对 ZBar 和自研的 C++ 条形码扫描库进行测试。在经过一段时间后,他们发现在某些 ZBar 超时的情况下,自研的 C++ 库却能够快速地得到扫描结果。显然,基于不同的条形码图像质量,这两个库的执行情况有所不同。
|
|||
|
|
|||
|
### 竞争取胜
|
|||
|
|
|||
|
为了能够同时利用 ZBar 和自研的 C++ 库,eBay 技术团队选择了一个“特殊的方案”。我想你肯定也能够猜到方案的大致内容。
|
|||
|
|
|||
|
在这个方案中,应用会启动两个工作线程,一个用于 ZBar,另一个用于自研的 C++ 库,两者同时对接收到的视频流进行处理。当主线程接收到有效的识别结果时,便结束所有工作线程的执行。若超时,则显示错误信息。
|
|||
|
|
|||
|
经过测试,条形码在不同模拟测试场景中的识别成功率,可以提高到 95%。
|
|||
|
|
|||
|
无独有偶的是,当尝试把 JavaScript 版本的条形码扫描器实现同样作为工作线程,加入到竞争“队列”中时,整个应用的条形码扫描识别成功率达到了将近 100%。这样的结果让人感到惊喜。应用的最终架构可以通过下图很好地进行展示。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/8b/cb/8b317339247449e611fd0c0711e1d7cb.png)
|
|||
|
|
|||
|
产品上线后的最终效果如下图所示。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/f4/1b/f47db948d3d7yyda6ce7fa45a47a211b.png "图片来源于 eBay 官方博客")
|
|||
|
|
|||
|
产品在上线使用了一段时间后,eBay 技术团队对应用的条形码扫描情况进行了统计,结果发现有 53% 的成功扫描来自于 ZBar;34% 来自于自研的 C++ 库。剩下的 13% 则来自于第三方的 JavaScript 库实现。可见,其中通过 Wasm 实现(自研 C++ 库、Zbar)得到的扫描结果占据了总成功次数的 87%。
|
|||
|
|
|||
|
虽然文章中没有提及,但实际上,设备对 Wasm 的兼容性也是需要考量的一个因素。你可以思考一下,我们怎样做可以在上述方案的基础上,来同时兼容旧设备上的条码扫描功能。
|
|||
|
|
|||
|
## AutoCAD Web
|
|||
|
|
|||
|
第二个我们要介绍的案例来自于一个有着将近 40 年历史的知名设计软件 —— AutoCAD。
|
|||
|
|
|||
|
AutoCAD 是一款由 Autodesk 公司设计研发的,用于进行 2D 绘图设计的应用软件,它被广泛地用于土木建筑、装饰装潢、工业制图等多个领域中。相信大部分的工科同学,也一定在大学本科期间参与过 AutoCAD 的课程与相关考试。如下图所示,是该应用桌面端版本的运行截图。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/52/4c/526a41fafae9ffafa80e2c152dd55b4c.png "图片来源于 YouTube")
|
|||
|
|
|||
|
### 历史负担
|
|||
|
|
|||
|
AutoCAD 桌面端软件的发展有着将近 40 年的历史。而随着应用的不断发展,随之而来便是逐渐变大的代码库体积,以及不断复杂化的软件架构。截止 2018 年,AutoCAD 桌面端应用已经有着超过 1500 万行的 C/C++ 代码,并且仍然在以肉眼可见的速度增长着。
|
|||
|
|
|||
|
### 移动互联网浪潮
|
|||
|
|
|||
|
随着 2008 年移动互联网浪潮的逐渐兴起,越来越多的用户开始使用 PC 甚至是移动设备上的 Web 浏览器,来完成日常工作的一部分内容。感知到趋势的 Autodesk ,便开始着手将自家的 AutoCAD 应用从 PC 端的原生应用逐渐向 Web 应用进行移植。
|
|||
|
|
|||
|
初期,由于 AutoCAD 原生应用本身的代码库过于庞大,AutoCAD 团队决定从头开始编写 AutoCAD 的 Web 版应用。在当时那个年代,HTML5 刚刚标准化,浏览器在功能特性上的支持还不够全面,并且跨浏览器的兼容性也很难得到保障。因此,AutoCAD 移植 Web 应用的第一版本便是基于 Adobe Flash 重新编写的,这个应用发布于 2010 年。
|
|||
|
|
|||
|
为了能够进一步利用 Web 标准,来优化 AutoCAD Web 应用的性能,并使得整个 Web 应用的技术架构更加贴近基于 JavaScript 构建的 Web 应用标准,AutoCAD 团队于 2013 开始着手进行 AutoCAD 标准 Web 应用的移植工作。并且此时的 AutoCAD 团队还有着更大的“野心”。
|
|||
|
|
|||
|
他们首先基于 C++ ,重写了为 iOS 移动端 Native 应用准备的轻量版代码库。然后通过交叉编译(Tangible)的方式,将这些 C++ 代码编译为了 Java 代码供 Android 设备使用。最后,在 Google Web Toolkit(一个 Google 开发的可以使用 Java 语言开发 Web 应用的工具集)的帮助下,又将这些 Java 代码转译为了 Web 平台可用的 JavaScript 代码。
|
|||
|
|
|||
|
但事实上,由于 GWT 本身作为转译工具,会产生很多额外的胶水代码,并且经由 C++ 交叉编译而来的 Java 代码本身质量也并不高,因此这导致了最后生成的 Web 应用代码库十分庞大,且在浏览器中的运行性能并不可观。这个“粗糙版”的 Web 应用发布于 2014 年。
|
|||
|
|
|||
|
时间来到 2015 年,彼时 ASM.js 作为 Wasm 的“前辈”正展露着头角。AutoCAD 团队借此机会,在 Emscripten 工具链的帮助下,直接从 AutoCAD PC 版原有的 C++ 代码库中移植了一部分主要功能到 Web 平台上,ASM.js 所带来的性能提升,让团队对 AutoCAD Web 应用的进一步发展充满了期待。
|
|||
|
|
|||
|
2018年3月,基于 Wasm 构建的 AutoCAD Web 应用诞生。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/86/1a/8666fd68e6658aede43b4c2814fc1b1a.png "图片来源于网络")
|
|||
|
|
|||
|
### 应用架构
|
|||
|
|
|||
|
整个 AutoCAD Web 应用的组成结构你可以参考下面这张图。在应用的右侧是绘图区域,该区域由 HTML 中的 Canvas 元素与相关 Web API 进行渲染,运行在独立工作线程中的 Wasm 模块实例则负责控制这部分区域的实际绘图效果。
|
|||
|
|
|||
|
左侧的 UI 控制区域由 TypeScript 基于 React 框架进行构建,基于组件化的构建方式与我们日常开发的 Web 前端应用项目基本保持一致。UI 部分的交互操作则会通过 “postMessage” 等 Web API 通知到工作线程中的 Wasm 实例,并对输出到 Canvas 中的画面进行实时处理。
|
|||
|
|
|||
|
![](https://static001.geekbang.org/resource/image/9c/51/9c8ebbyy28f279363ce940699b096151.png "图片来源于 YouTube")
|
|||
|
|
|||
|
## 总结
|
|||
|
|
|||
|
好了,讲到这,今天的内容也就基本结束了。最后我来给你总结一下。
|
|||
|
|
|||
|
在这节课里,我们举了两个比较有代表性、在现实生活中的 Wasm 生产实践案例。第一个是 eBay 在其 Web H5 应用中添加的条形码扫描功能。eBay 技术团队在初期使用了第三方的 JavaScript 版本条形码识别库,来进行条形码的识别,但无奈识别成功率较低。
|
|||
|
|
|||
|
而随着后期 ASM.js 与 Wasm 的出现和普及,eBay 技术团队选择将自研的,原先被应用于 Native 平台的 C++ 识别库编译到 Wasm ,并整合到 Web 应用中使用。此时虽然识别成功率有所上升,但在某些成像质量较差的场景下,条形码仍然无法被正确识别。
|
|||
|
|
|||
|
为了解决这个问题,团队成员又以同样的方式,将基于 C 语言开发的知名第三方条形码识别库 ZBar 编译到了 Wasm。并通过多个工作线程“竞争”的方式,尝试同时整合 JavaScript 版本实现、ZBar 与自研的 C++ 识别库,让应用的整体识别成功率有了一个质的提高。
|
|||
|
|
|||
|
在第二个案例中,我们介绍了 AutoCAD 在移动互联网浪潮兴起的这十年时间里,不断尝试将其 Native 应用移植到 Web 平台所使用的一些方式。而在这些众多的方案中,基于 Wasm 的方案给予了 AutoCAD 能够在 Web 平台上流程运行的可能。
|
|||
|
|
|||
|
最后,希望这些真实的案例能够给予你对 Wasm 更多的信心和思考。
|
|||
|
|
|||
|
## **课后练习**
|
|||
|
|
|||
|
最后,我们来做一个思考题吧。
|
|||
|
|
|||
|
你觉得将 Native 应用移植到 Web 应用时可能会存在哪些问题呢?或者说 Native 应用与 Web 应用在执行流程或组成方式上有哪些区别呢?欢迎大家各抒己见。
|
|||
|
|
|||
|
今天的课程就结束了,希望可以帮助到你,也希望你在下方的留言区和我参与讨论,同时欢迎你把这节课分享给你的朋友或者同事,一起交流一下。
|
|||
|
|