WebAssembly在eBay的实践:速度提升50倍

2019 年 5 月 26 日 前端之巅

作者 | Pranav Jha,Senthil Padmanabhan
译者 | 王强

WebAssembly 自诞生以来就震动了整个前端业界。Web 社区很高兴看到 JavaScript 有了竞争者。何况原生 WebAssembly 的速度比 JavaScript 要快得多。我们 eBay 也身处这一浪潮之中,对 WebAssembly 非常欢迎。

我们的工程师们都对 WebAssembly 热情满满,并一直关注着它的规范和演变。当 WebAssembly 1.0正式发布,并支持所有主流浏览器时,eBay 内部的团队都迫不及待地去体验尝试了。

但有一个问题。虽然有许多用例和应用可以从 WebAssembly 中受益,但电子商务行业使用的技术依旧比较原始。我们很难找到适合 WebAssembly 的电商场景。我们也想出了一些例子,但这些场景中用 JavaScript 更合适些。

在 eBay,每当我们评估新技术时要问的第一个问题就是“它会给我们的客户增加哪些潜在价值?”,只有明确了这个答案我们才会继续下一步。人们很容易对闪闪发光的新事物着迷,却经常会忽略这些新事物其实可能对我们的客户没有任何好处,反而只会让现有的工作流程更加复杂。

用户体验永远高于开发者的体验。但 WebAssembly 却与众不同,它的确潜力巨大,我们只是没有合适的用户场景而已。终于,这种情况开始出现了变化。

条形码扫描

eBay 的 iOS 和 Android 原生应用应用程序在出售流程中都有条形码扫描功能。这个功能利用设备相机扫描产品的 UPC 条形码,并根据条形码数据自动填写表单,省去了手工填表的麻烦。

这是一个纯原生应用功能。它需要在设备上做一些高负载图像处理工作,检测来自摄像头图像流中的条形码编号。然后将检索到的代码发送到后端服务,再由后端服务填写表单。这意味着设备上的图像处理逻辑必须有很高的效率。

对于原生应用程序,我们将内部构建的 C ++ 扫描程序库编译为 iOS 和 Android 的本机代码,这个库能够以很高的性能从摄像头的图像流中识别产品条形码。我们正在慢慢过渡到 iOS 和 Android 的原生 API 上,但原来这个 C++ 库仍然很可靠。

条形码扫描为卖家提供的直观功能,它简化了填写表单的流程。但很遗憾,我们的移动浏览器端用户是用不了这个功能的。我们已经对移动浏览器端优化了商品销售流程,可惜还是没有条形码扫描功能,用户必须手动输入产品 UPC,显然这是很麻烦的体验。

为 Web 端提供条形码扫描功能

之前我们已经在计划过为 Web 端集成条形码扫描功能了。其实两年前我们就利用开源 JavaScript 库 BarcodeReader 做了一个带条形码扫描功能的 Web 版本。 问题是它只有在 20%的时间表现良好。 剩余的 80%的时间,它非常缓慢,用户还以为它卡死在那儿了。大多数情况下它都会超时。

这种结果倒是不奇怪,虽说 JavaScript 确实能做到和原生代码一样快,但前提是它处于“热路径”,即由 JIT 编译器做过大量优化。这里的麻烦是 JavaScript 引擎使用大量启发算法来判断代码路径是否为热路径,不能保证每个实例都有经过优化。这种不一致的设计显然会让用户非常失望,结果我们不得不禁用这个功能。

但如今情况不一样了:随着 web 平台的快速发展,我们再次产生了这个念头:“我们能否为 Web 端实现具备稳定并优异表现的条码扫描功能?”

有一个备选方案是等待 Shape Detection API。计划中的这个 Web API 会为 Web 带来许多原生图像检测功能,其中之一就是条形码检测。但它仍处于非常早期的发展阶段,在实现跨浏览器兼容性的路上还有很长的路要走。就算将来它实现了多浏览器兼容,也不能保证在所有平台上都能正常工作。所以我们必须考虑其它选择。

于是我们想到了 WebAssembly。如果我们能使用 WebAssembly 实现条形码扫描功能,毫无疑问它的表现会非常稳定。WebAssembly 字节码的强类型和结构使得编译器始终处于热路径上。最重要的是我们有一个现成的为原生应用服务的 C++ 库。将 C++ 库编译为 WebAssembly 是很方便的。于是乎我们觉得方案已经很清楚了,没什么障碍可言,但实际做起来还是没那么容易的。

架构

我们基于 WebAssembly 实现的条形码扫描功能的工程设计非常简单直观。

  • 使用 Emscripten编译 C++ 库,生成 JavaScript 粘合代码和.wasm 文件。

  • 从主线程创建一个 Worker 子线程。该子线程将导入生成的 JavaScript 粘合代码,然后实例化.wasm 文件。

  • 主线程将从相机捕获的图像流中截取快照发送到 Worker 子线程,子线程将通过粘合代码调用相应的 wasm API。API 的响应会传递给主线程。响应可以是 UPC 字符串(传递给后端),如果没有检测到条形码则为空字符串。

  • 快照是空场景时会重复上述步骤,直到检测到条形码。此循环由可配置的阈值(以秒为单位)计时。达到阈值后,我们会显示一条警告消息“这不是有效的产品代码。请尝试其它条形码或输入文本搜索“。出现这种情况可能是因为用户没有拍到有效的条形码,或者是条形码扫描功能的性能不够。我们会追踪这些超时实例,因为它可以很好地反映条形码扫描的性能表现。

WebAssembly 工作流

编译

不管是什么 WebAssembly 项目,第一步就是要有一个定义良好的编译管道。Emscripten 已成为编译 WebAssembly 的工具链事实标准,但关键是要有一个一致的环境来输出确定的内容。

我们的前端基于 Node.js,这意味着我们需要一个适用于 npm 流程的解决方案。还好就在那时候 Surma Das 发表了“Emscripten 和 npm”这篇文章。文中介绍的基于 Docker 的编译 WebAssembly 的方法非常有用,因为它节省了大量开销。根据该文的建议,我们使用了 trzeci 制作的 Docker Emscripten 镜像。我们还得对自己开发的 C++ 库做一些调整,这样才能顺利编译到 WebAssembly。这个过程其实是一次试错练习。最后我们不仅编译成功了,还在现有的构建流程中建立了一个简洁的 WebAssembly 工作流程。

相关链接:

Emscripten 和 npm文章路径: https://developers.google.com/web/updates/2019/01/emscripten-npm 

它很快,但......

我们评测扫描组件性能的方法是分析 API 一秒钟内可以处理的帧数。Wasm API 会接收来自相机图像流的快照像素数据,然后执行计算并返回响应结果。这个过程是连续不断的,直到检测到条形码才结束。我们的指标是大家熟悉的每秒帧数(FPS)。

在我们的测试中,WebAssembly 的平均输出高达 50FPS。问题是在时限内它只有 60%的扫码成功率。就算 FPS 如此之高,它也无法在规定的时间内有效检测出剩余的 40%条形码,结果只会显示失败警告。

作为对比,我们之前试过的 JavaScript 版本绝大多数时间输出只有 1FPS。所以可以肯定的是 WebAssembly 速度更快(足足 50 倍提升),但不知何故,它在近一半的扫描中无法在规定的时限内检测到条形码。还应该提到的是,在某些情况下 JavaScript 表现得非常好,能够立即检测到条形码。一个可行的解决办法是晚点再显示失败提示,但这只会增加用户的挫败感,而且实际上并没有真正解决问题,所以我们没采纳这个主意。

一开始我们也搞不清楚为什么之前做的 C++ 库(在原生应用里表现优异)在 Web 平台上就没法输出相同的结果。经过大量的测试和调试,我们发现扫码成功所需的时间取决于对焦焦距和背景阴影。

那在原生应用中是如何处理图像的呢?原来在原生平台上,我们调用了平台内置的 API 来自动对焦,或让用户选择焦点,对焦到正扫描对象的中心上。这就让原生应用能够持续向扫码组件库发送高质量的图像像素数据(也就是只包含条形码的信息),避免了图像模糊的情况。因此原生平台上的响应延迟是基本不变的。

现在我们明白问题出在哪里了。我们认为在不同的对焦条件下,可能有时其它原生扫码库的表现会更好些。开源条形码扫描组件 ZBar 就非常受欢迎,版本也稳定;更重要的是它更适合模糊和颗粒状的图像场景,那么为什么不试试呢?

由于我们已经搭建好了 WebAssembly 工作流程,所以就能无缝将 ZBar 用 WebAssembly 编译并部署成功。然后我们开始评估 ZBar 实现的性能。它的表现不错,输出大约 15 FPS(不如我们自己的 C++ 库)。但在同样的时限内 ZBar 的成功率接近 80%。这比我们自制的 C++ 库提升不少,但仍然无法做到 100%的成功率。

我们对这个结果还是不满意,但却注意到了一些意想不到的情况。在 ZBar 没能成功的情况下,自制的 C++ 库却能够非常快速地完成工作。这是一个令人心动的惊喜。很显然,两个库各自适合不同质量的图像快照。于是我们萌生了一个想法。

多线程和扫码竞赛

你可能猜到了:为什么不创建两个 Web worker 线程呢,一个用于 ZBar,一个用于自制 C++ 库,然后让它们互相竞争就行了。获胜的响应(也就是第一个发送有效条形码的响应)会被发送到主线程,同时所有 worker 线程都终止运行。我们做出了这套方案,然后开始内部场景测试并模拟尽可能多的场景。结果我们的成功率提升到了 95%,比以前好多了——但仍然不足 100%。

有人提了一个奇特的建议,就是把原来的 JavaScipt 库也加进来。这样方案就变成了三个线程并行。老实说我们并不认为这会解决问题,但我们尝试一下是很容易的,因为我们标准化了工作者界面。

又一次让完全出乎我们意料的是,三条线程相互竞争后整体成功率确实接近了 100%。正如之前所提到的那样,JavaScript 在某些情况下表现非常好,而正是这个因素提升了整体成功率。所以“总要试试 JavaScript”这句话真没错啊!开个玩笑。下面的图表展示了我们最终实现的架构。

基于Web的条形码扫描仪架构

下图是高层流程图:

条形码扫描组件流程图

关于资产加载的说明

主页面呈现完毕之后就会预加载条形码扫描组件所需的资源。这是为了确保售货页面(测试页面)能快速加载,准备好交互流程。

我们使用 XMLHttpRequest 在页面的加载事件之后预取并缓存 WebAssembly 资源(wasm 文件和相应的粘合代码脚本)和 JavaScript 扫码库。这里要注意的是它们只是预加载,并不会执行。

这是为了让主线程专注于用户交互任务。只有当用户点击条形码图标时它们才会开始执行。如果用户在资源加载完毕之前就点击了条形码图标,我们会根据需要加载它们并立即执行。条形码事件处理程序和 worker 控制器绑定在一起,都是初始页面加载的内容,但它们的尺寸都很小。

成果

经过全面测试和内部测试,这个功能进入了 A/B 测试流程。实验中的“测试”页面会显示条形码扫描图标(如下面的屏幕截图所示),“Control”页面是没有的。

最终产品

用于评估这个 A/B 测试成功与否的指标称为“草稿完成率”。它是一份表单从草稿状态成功完成并提交的比例。草稿完成率是一个很好的指标,可以条形码扫描组件是不是真的通过提升表单完成率来减少了用户摩擦并优化了卖货流程(测试的页面功能)。

我们做了几周的测试,结果回来时确实非常令人满意。它与我们最初的假设完全一致。在启用条形码扫描组件的情况下,填单流程的草稿完成率提高了 30%

A/B 测试结果

我们还加入了性能评估,来分析各种类型的扫描组件获胜的比例。结果与预期一致,ZBar 占到扫描成功结果的 53%,其次是自制的 C++ 库,占 34%;最后是 JavaScript 库,占 13%。

结论

这趟 WebAssembly 旅程对我们来说是一次很棒的学习经历。工程师们都热衷于新技术,都想立即尝试一下。如果这些新技术能对以客户为中心的指标产生积极影响,那就是两全其美了。回到本文一开始谈到的观点:技术发展非常迅速,每天都有新事物出现,但只有少数会给客户带来积极影响,而 WebAssembly 就是其中之一。这就是我们从这次实践中学到的最有价值结论:

要对 99 件事情说“不”,而对客户真正重要的那一件事,我们的答案就应该是“Yes”。

至于将来的发展,我们正在考虑将条形码扫描组件扩展到移动 Web 平台的买货流程里,rang 买家可以扫描物品来搜索和购物。我们还将研究使用形状检测 API 等浏览器内置的摄像头功能来做改进。同时,我们很高兴 eBay 团队为 WebAssembly 找到了合适的用例,并将这项技术引入了电子商务业。

特别感谢 Surma Das 和 Lin Clark 为 WebAssembly 撰写的诸多文章,帮助我们跨越了许多障碍。

英文原文: https://www.ebayinc.com/stories/blogs/tech/webassembly-at-ebay-a-real-world-use-case/

活动推荐

直播已成为许多人日常生活中的重要一环,但是直播背后的技术却鲜为人知。像世界杯、阅兵、英雄联盟赛事等大流量直播,如何确保高清晰及低延迟的效果?语音连麦场景如何保证通话清晰?直播内容审核、直播封面截取等场景如何实现流程的 AI 自动化?

6 月 29 日 13:30-18:00,腾讯云音视频及融合通信技术北京沙龙上,腾讯云的多位技术专家将会到场与你分享“直播遇见云”背后的技术故事。扫描【下图二维码】或者点击【阅读原文】即可查看本期沙龙的的详细日程。


登录查看更多
0

相关内容

与淘宝网相类似,人们可以通过这一网络平台向他人出售商品及服务。
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
FPGA加速系统开发工具设计:综述与实践
专知会员服务
65+阅读 · 2020年6月24日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
德勤:2020技术趋势报告,120页pdf
专知会员服务
190+阅读 · 2020年3月31日
阿里巴巴达摩院发布「2020十大科技趋势」
专知会员服务
106+阅读 · 2020年1月2日
微信小程序支持webP的WebAssembly方案
前端之巅
19+阅读 · 2019年8月14日
阿里技术大牛:一份架构师成神路线图!
51CTO博客
30+阅读 · 2019年7月6日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
四大维度全景揭秘阿里巴巴智能对话开发平台
云栖社区
11+阅读 · 2019年1月15日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
【物联网】物联网产业现状与技术发展
产业智能官
15+阅读 · 2018年12月17日
大伽「趣」说AI:腾讯云在多个场景中的AI落地实践
人工智能头条
4+阅读 · 2018年8月1日
一篇文章读懂阿里企业级数据库最佳实践
阿里巴巴数据库技术
5+阅读 · 2017年12月20日
猝不及防!刚刚,马云重大宣布,沸腾了全中国!
今日互联网头条
3+阅读 · 2017年11月23日
Vue.js 很好,但是比 Angular 或 React 更好吗?
程序猿
3+阅读 · 2017年8月27日
Arxiv
21+阅读 · 2019年8月21日
VIP会员
相关VIP内容
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
FPGA加速系统开发工具设计:综述与实践
专知会员服务
65+阅读 · 2020年6月24日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
德勤:2020技术趋势报告,120页pdf
专知会员服务
190+阅读 · 2020年3月31日
阿里巴巴达摩院发布「2020十大科技趋势」
专知会员服务
106+阅读 · 2020年1月2日
相关资讯
微信小程序支持webP的WebAssembly方案
前端之巅
19+阅读 · 2019年8月14日
阿里技术大牛:一份架构师成神路线图!
51CTO博客
30+阅读 · 2019年7月6日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
四大维度全景揭秘阿里巴巴智能对话开发平台
云栖社区
11+阅读 · 2019年1月15日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
【物联网】物联网产业现状与技术发展
产业智能官
15+阅读 · 2018年12月17日
大伽「趣」说AI:腾讯云在多个场景中的AI落地实践
人工智能头条
4+阅读 · 2018年8月1日
一篇文章读懂阿里企业级数据库最佳实践
阿里巴巴数据库技术
5+阅读 · 2017年12月20日
猝不及防!刚刚,马云重大宣布,沸腾了全中国!
今日互联网头条
3+阅读 · 2017年11月23日
Vue.js 很好,但是比 Angular 或 React 更好吗?
程序猿
3+阅读 · 2017年8月27日
Top
微信扫码咨询专知VIP会员