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 上发表的许多文章。它的确帮忙咱们在各种状况下畅通无阻。