发布在即!企业 AIGC 应用程度测评,3 步定制专属评估报告。抢首批测评权益>>> 了解详情
写点什么

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

  • 2019-05-26
  • 本文字数:4671 字

    阅读完需:约 15 分钟

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

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


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

它很快,但…

我们评测扫描组件性能的方法是分析 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”这句话真没错啊!开个玩笑。下面的图表展示了我们最终实现的架构。



下图是高层流程图:


关于资产加载的说明

主页面呈现完毕之后就会预加载条形码扫描组件所需的资源。这是为了确保售货页面(测试页面)能快速加载,准备好交互流程。我们使用 XMLHttpRequest 在页面的加载事件之后预取并缓存 WebAssembly 资源(wasm 文件和相应的粘合代码脚本)和 JavaScript 扫码库。这里要注意的是它们只是预加载,并不会执行。这是为了让主线程专注于用户交互任务。只有当用户点击条形码图标时它们才会开始执行。如果用户在资源加载完毕之前就点击了条形码图标,我们会根据需要加载它们并立即执行。条形码事件处理程序和 worker 控制器绑定在一起,都是初始页面加载的内容,但它们的尺寸都很小。

成果

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



用于评估这个 A/B 测试成功与否的指标称为“草稿完成率”。它是一份表单从草稿状态成功完成并提交的比例。草稿完成率是一个很好的指标,可以条形码扫描组件是不是真的通过提升表单完成率来减少了用户摩擦并优化了卖货流程(测试的页面功能)。我们做了几周的测试,结果回来时确实非常令人满意。它与我们最初的假设完全一致。在启用条形码扫描组件的情况下, 填单流程的草稿完成率提高了 30%



我们还加入了性能评估,来分析各种类型的扫描组件获胜的比例。结果与预期一致,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/



2019-05-26 15:4510490

评论 2 条评论

发布
用户头像
2019-05-27 10:28
回复
用户头像
思路很好玩,期待github上有sample
2019-05-27 09:47
回复
没有更多了
发现更多内容

浅谈安全测试之AppScan

数新网络官方账号

大数据 安全测试

软件测试|码农必会的git操作(一)

霍格沃兹测试开发学社

开发者们:618电商团战即将开启,“抢流量”想上分,必备这三个大招 | MobTech观察

MobTech袤博科技

软件测试|Python操作Excel制作报表,不要太方便

霍格沃兹测试开发学社

理解 JVM 工作机制(八) JVM 性能调优

ue4

Java jdk JVM

基于低代码平台的多租户解决方案

力软低代码开发平台

MobTech 秒验|运营商网关取号​

MobTech袤博科技

软件测试|pyecharts绘制NBA球星得分能力对比图

霍格沃兹测试开发学社

人体识别图像技术在智能家居中的应用

来自四九城儿

理解 JVM 工作机制(十一) 类加载器

ue4

Java jdk JVM

低代码开发:构建企业数字化生态系统的秘密武器!

加入高科技仿生人

低代码 数字化 企业数字化 数字转型

异地组网——ZeroTier

数新网络官方账号

大数据

腾讯广告技术版图|把脉生意困局,助力全域经营

科技热闻

塞尔达工业革命卷到数字电路了!网友:怕不是要在Switch里造Switch

Openlab_cosmoplat

伟大的公司只需要十一人

Openlab_cosmoplat

开源 MidJourney

理解 JVM 工作机制(四) 回收策略

ue4

Java jdk JVM

理解 JVM 工作机制(六) 垃圾收集器

ue4

Java jdk JVM

激发创造力!如何轻松录制PPT和人像视频

淋雨

PPT Camtasia 录屏

软件测试/测试开发丨自动化测试定位策略实战-测试人论坛搜索

测试人

程序员 软件测试 自动化测试 测试开发

我国首例汽车企业全业务场景数据出境安全评估获批

Openlab_cosmoplat

大数据 开源 汽车

软件测试|简单易学的性能监控体系prometheus+grafana搭建教程

霍格沃兹测试开发学社

理解JVM工作机制(五)垃圾回收算法

ue4

Java jdk JVM

面试进阶齐飞!Github一天10w赞的阿里Java系统性能优化笔记有多牛?

程序员小毕

JVM 面试题 架构师 java面试 Java性能优化

Sprint回顾会及Scrum工具

顿顿顿

Scrum 敏捷开发 敏捷项目管理 敏捷开发管理工具 sprint回顾会

聊聊 Milvus GC:从一次数据丢失事件展开

Zilliz

非结构化数据 GC Milvus 向量数据库

点云分割技术的发展现状及挑战

来自四九城儿

软件测试|Python实用炫酷技能——推导式

霍格沃兹测试开发学社

3D设计必备!5个免高质量的 HDRI 环境贴图网站

Finovy Cloud

blender

理解 JVM 工作机制(七) 内存分配和回收策略

ue4

Java jdk JVM

理解 JVM 工作机制(九) 类文件结构

ue4

Java jdk JVM

理解 JVM 工作机制(十) 类加载机制和加载的过程

ue4

Java jdk JVM

WebAssembly在eBay的实践:速度提升50倍_语言 & 开发_Pranav Jha,Senthil Padmanabhan_InfoQ精选文章