eBay上的 WebAssembly:一个真实的用例
五月22,2019
作者:Senthil Padmanabhan和Pranav Jha
从发布之日起,WebAssembly 就在前端世界引起了巨大轰动。Web 社区欣然接受了接受用 JavaScript 以外的其他编程语言为浏览器编写的并运行代码的想法。首先,WebAssembly 始终保证本机速度比 JavaScript 快得多。在我们的 eBay 上也一样。
我们的工程师对此想法感到非常兴奋,并一直关注着规格及其发展。一旦将WebAssembly 1.0 运用于所有主流浏览器后,eBay周围的团队就渴望尝试一下。
有一个问题:虽然有很多用例从 WebAssembly 中受益,但在电子商务中的技术范围仍然很显得很原始。我们找不到合适的用例来利用 WebAssembly。提出了一些建议,但是 JavaScript 本身就更好。在 eBay 上,当我们评估新技术时,我们要问的第一个问题是“这为我们的客户增加了哪些潜在价值?” 除非对此有明确的了解,否则我们不会继续进行下一步。很容易被新的闪亮事物带偏,常常忘记这一事实可能对我们的客户没有任何影响,而只会使现有的工作流程变得复杂。用户体验始终胜过开发人员体验。但是 WebAssembly 是不同的。它具有巨大的潜力,我们只是没有合适的用例。好吧,最近发生了变化。
条形码扫描仪
iOS 和 Android 上的 eBay 本地应用程序在销售流程中均具有条形码扫描功能。用设备摄像头扫描产品 UPC 条形码并自动填写表单,从而消除了人工开销。这是仅本机应用程序的功能。需要在设备上进行一些密集的图像处理才能从相机流中检测条形码编号。然后将检索到的代码发送到后端服务,完成表单填写。这意味着设备上的图像处理流程必须具有很高的性能。对于本机应用程序,我们将内部构建的 C++ 扫描程序库编译为适用于 iOS 和 Android 的本机代码。从相机流识别出产品条形码的性能非常好。我们正在逐步过渡到 iOS 和 Android 本机 API,但是 C++ 库仍然是最可靠的。
条形码扫描对我们的卖家来说是一种直观的功能,因为它使表单流程更加流畅。不幸的是,我们的 Web 移动端用户未启用此功能。除了没有条形码扫描,我们已经为 Web 移动端提供了优化的销售流程,而卖方必须手动输入产品UPC,从而增加了使用的阻力。
移动 Web 端条码扫描
之前,我们已经研究过为移动网络实施条形码扫描功能。实际上,我们使用开源 JavaScript 库BarcodeReader推出了条形码扫描功能。这是两年前的事了。问题在于它仅在20%的时间内表现良好。其余80%的时间非常慢,用户认为它已损坏。超时是常态。这也不出所料,因为 JavaScript 确实可以和本机代码一样快,但是仅当它处于“热路径”时,即由JIT进行了优化的编译。根源在于,JavaScript 引擎使用大量启发式方法来确定代码路径是否“热”,并且不能保证每个实例都得到优化。这种不一致显然令用户很无奈,因此我们不得不禁用了该功能。但是现在情况有了新变化。随着 Web 平台的快速发展,这个问题重新浮出水面:“我们能否为 Web 实施性能一致的条形码扫描功能?”
一种选择是等待Shape Detection API。提出的 Web API 为 Web 带来了许多本机图像检测功能,其中之一是条形码检测。但还处于起步阶段,要实现跨浏览器兼容性还有很长的路要走。即使这样,也不能保证在每个平台上都能正常使用。因此,我们必须考虑其他选项。
这恰是 WebAssembly 发挥期所长地方。如果在 WebAssembly 中实现了条形码扫描功能,我们可以保证其性能始终如一。WebAssembly 字节码的强类型和不变结构,使编译器始终停留在热路径上。最重要的是,我们有一个现有的 C++ 库正在为本机应用程序完成这项工作。C++ 库是编译成WebAssembly 的理想选择。我们认为我们面前有一条光明的大道。...好吧,也许还不完全是。
构建
我们实现基于 WebAssembly 的条形码扫描的项目设计非常简单。
- 使用Emscripten编译 C++ 库。这将生成 JavaScript 粘合代码 和
.wasm
文件。 - 从主线程创建一个工作线程。辅助 JavaScript 将导入生成的 JavaScript 粘合代码,从而实例化
.wasm
文件。 - 主线程将摄像机视频流发送快照到工作线程,工作线程~~~~将通过粘合代码调用相应的 wasm API。API 的响应传递到主线程。如果未检测到条形码,则响应可以是 UPC 字符串(传递给后端),也可以是空字符串。
- 对于空情况,重复上述步骤,直到检测到条形码。该循环由可配置的阈值(以秒为单位)计时。达到阈值后,我们将显示警告消息_“这不是有效的产品代码。请尝试其他条形码或按文本搜索。”_ 这意味着用户没有关注有效的条形码,或者条形码扫描功能的性能不足。我们跟踪这些超时实例,因为它很好地指示了条形码扫描仪的性能。
WebAssembly工作流程
汇编
任何 WebAssembly 项目的第一步都是拥有定义明确的编译管道。Emscripten 已成为事实上的用于编译 WebAssembly 的工具链,但是关键是要有一个一致的环境来产生确定性的输出。我们的前端基于 Node.js,这意味着我们需要一个与 npm 工作流程一起使用的解决方案。幸运的是,大约在同一时间,Surma Das发表了“ Emscripten and npm ”一文。基于Docker的 WebAssembly 编译方法非常合理,因为它消除了大量的开销。在文章中建议,我们用由trzeci提供的 Emscripten 的Docker镜像。我们必须对自定义 C++ 库进行一些调整,以使其兼容于WebAssembly。所谓调整就主要是反复试验。最终,我们能够进行编译,并且能够在我们现有的构建管道中建立简洁的 WebAssembly 工作流。
很快,但是…
我们计算扫描功能的性能的方法是通过分析 wasm API 每秒可以处理的帧数。即 wasm API 接收一帧来自实时摄像机流的图像快照像素数据,执行计算并返回响应。连续进行此操作,直到检测到条形码为止。我们以众所周知的【每秒帧数】(FPS)度量标准对其进行度量。
在我们的测试中,WebAssembly 实现平均以惊人的 50 FPS 执行。但是,在当时的超时阈值下,仅有60%的情况能正确识别。即使具有如此高的 FPS,它也无法快速检测出剩余40%有效扫描的条形码,最终只能无奈显示警告消息。为了进行比较,我们之前尝试的 JavaScript 实施大多数情况下仅以1 FPS 执行。因此,可以肯定的是,WebAssembly 速度更快(50倍),但由于某种原因,它无法在分配的时间内检测到近一半的条形码。还应该提到的是,在某些情况下,JavaScript 表现非常出色,并且能够立即检测到条形码。一种明显的选择是延迟显示警告消息,但这只会增加用户的沮丧感,而且我们实际上并没有解决真正的问题。所以我们放弃了这个主意。
最初,我们不知道为什么对于本机应用程序运行良好的自定义 C++ 库无法在 Web 上产生相同的结果。经过大量的测试和调试,我们发现聚焦对象的角度以及背景阴影决定了成功检测的时间。那它如何在本机应用程序中工作?好吧,在本机应用程序中,我们使用内置的 API 来自动聚焦或将用户的点击聚焦提供给被扫描对象的中心。这使本机应用程序可以始终将高质量的图像像素数据(即仅有关条形码的信息)发送到扫描库。这避免了图像模糊的情况。因此,始终如一的快速响应时间。
既而我们对这件事情有了一个认识,我们认为也许其他的本地库在不同的聚焦条件下可能会表现更好。开源条形码阅读器ZBar非常受欢迎且稳定。更重要的是,它适用于模糊和颗粒状图像。为什么不试试呢?由于我们已经建立了 WebAssembly 工作流程,因此可以无缝编译和部署 ZBar,因为WebAssembly 是无缝的。然后,我们开始评估 ZBar 实现。性能不错,大约 15 FPS(不如我们的自定义 C++ 库)。但是,对于相同的超时阈值,成功率接近80%。绝对是对我们自已的 C++ 库的改进,但仍不是100%可靠。
我们仍然对结果不满意,但是我们发现了一些意想不到的事情。在 ZBar 超时的情况下,自定义 C++ 库能够非常快地完成工作。这真是一个惊喜。显然,基于图像快照的质量,这两个库的执行情况有所不同。这带给了我们一个新办法。
用多线程再抢救一下
您可能猜对了。为什么不创建两个 Web Worker 线程-一个用于 ZBar,一个用于自定义 C++ 库-并使它们相互竞争。中奖响应(即第一个发送有效条形码的响应)被发送到主线程,并且所有工作线程都被终止。我们进行了设置,并开始进行内部测试,以模拟尽可能多的情况。当扫描有效的条形码时,此设置为我们提供了95%的成功率。比我们以前的成功率要好得多,但仍不到100%。
一个奇怪的建议是也将原始的 JavaScipt 库加入了组合。这将使其成为三个线程。老实说,我们认为这不会有所收获。但是,由于我们对工作人员界面进行了标准化,因此很容易尝试。令我们惊讶的是,三个线程相互竞争,成功率确实接近 100%。这又是完全出乎意料的。正如文章前面提到的那样,JavaScript 在某些情况下确实表现良好,并且这个因素似乎可以弥补上面的差距。因此,是的,“始终押在 JavaScript 上。” 开个玩笑,下图很好地概述了我们实现的最终体系结构。
基于Web的条形码扫描仪架构
下图显示了高级流程图:
条形码扫描仪流程图
关于资源文件加载的说明
呈现主页后,将预取条形码扫描功能所需的资源。这是为了确保快速加载卖家着陆页,并准备进行交互。在页面的加载事件之后,使用XMLHttpRequest预取并缓存 WebAssembly 资源文件(wasm文件和关联的粘合代码脚本)和 JavaScript 扫描功能库 。这里要注意的一点是它们没有执行。这是为了使主线程保持空闲以进行用户交互。仅当用户点击条形码图标时才会执行。如果用户在加载资源文件之前点击条形码图标,我们将按需加载并立即执行。条形码事件处理程序和工作线程控制程序捆绑在一起作为初始页面加载的一部分,但是它们的尺寸很小。
结果
经过全面测试和内部测试后,该功能作为 A/B 测试启动。实验中的“测试方”输入区显示了条形码扫描仪图标(下面的屏幕截图),而“控制方”没有显示。
最终产品
用来评估 A/B 测试成功与否的度量标准称为“草稿完成率”。这是列表从草稿阶段到成功完成并提交的比例。草案完工率是一个很好的指标,可以支持以下观点:减少使用阻力并证明通过条形码扫描仪的无缝销售流应该能够完成更多清单。我们将测试运行了几个星期,当结果返回时,确实非常令人满意。它与我们最初的假设完全一致。启用条形码扫描器后,表单流程的草稿完成率提高了30%。
A/B 测试结果
我们还添加了埋点,以获取有关哪种类型的扫描功能获胜的信息。结果符合预期,ZBar 占成功扫描的 53%,其次是自定义 C++库,占34%,最后是JavaScript 库,占13%。
结论
WebAssembly 的整个旅程对我们来说是一次很棒的学习经历。工程师对新技术感到非常兴奋,并立即想尝试一下。如果相同的技术对以客户为中心的指标产生积极的影响,那将是双重荣幸。这暗示了本文的早期观点。技术发展非常迅速。每天我们都会听到新事物的发布。但是只有少数几个对客户有所帮助,WebAssembly 就是其中之一。这是我们从此练习中学到的最大经验- “对99件事情说【不】,对客户真正重要的一件事情说【是】。”
下一步,我们正在考虑将条形码扫描功能扩展到移动端的买家,这将使买家可以扫描物品进行搜索和购买。我们还将研究通过 Shape Detection API 和其他浏览器内置摄像头功能来增强此功能。同时,我们很高兴在eBay 上找到了 WebAssembly 的正确用例,并将该技术引入电子商务。
特别感谢Surma Das和Lin Clark在 WebAssembly 上发表的许多文章。它确实帮助我们在各种情况下畅通无阻。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。