HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

WebAssembly:解决 JavaScript 痼疾的银弹?

  • 2017-07-19
  • 本文字数:4321 字

    阅读完需:约 14 分钟

《没有银弹》是 Fred Brooks 在 1987 年所发表的一篇关于软件工程的经典论文。该论文的主要论点是,没有任何一项技术或方法可以能让软件工程的生产力在十年内提高十倍。 在 Web 开发这一领域,由于 JavaScript 一直存在着诸多从本质上来看无法解决的问题,那么解决 JavaScript 痼疾的银色子弹是否存在呢?

作为一门仅用了十天进行设计的语言,Brendan Eich 一定没有想到 JavaScript 会从一门简单的”脚本“发展成为 Web 开发的主宰,并且如火如荼的渗透到桌面或移动客户端开发甚至服务器端开发中。

之所以 JavaScript 如此火爆,甚至有了著名的 Atwood 定律,声称"任何可以使用 JavaScript 来实现的应用都最终都会使用 JavaScript 实现",主要原因在于两点:

  1. Web 应用可以越来越多的代替传统的客户端应用程序;
  2. JavaScript 引擎的运算速度大幅改进。

从辩证的角度来看,上面两个观点其实是相互影响、相互促进的关系。JavaScript 引擎运算速度的逐步提升,导致了一部分简单的客户端应用可以被 Web 应用代替,进而用户和开发者都希望更多复杂的客户端应用也用 Web 实现,进而促进 JavaScript 引擎运算速度的进一步提升。

Google V8 引擎是 JavaScript 引擎性能改善的现象级项目。V8 于 2007 年发布,由于采用了 JIT ( just-in-time)技术,V8 引擎可以将 JavaScript 代码在运行前编译为机器语言,这样运行速度就会有大幅提升。

客观存在的问题是,开发者对性能的要求是近乎无止境的,而基于 JIT 的设计思路带来的性能已经逐渐被挖掘到了极限,并且逐步暴露出了一些难以解决的问题。

首先是被称为“重优化”的性能瓶颈。编译为机器代码的前提条件是必须让编译器清晰的知道变量的类型,偏偏 JavaScript 是一门弱类型语言,参考如下代码:

复制代码
function sum ( a , b ) {
return a + b;
}
sum(1,2);
sum("1","2");

V8 引擎在运行这段代码时,在第一次调用 sum 函数时,由于传递的类型是两个数字,所以会将 sum 这个函数的参数设置为数字类型并编译为机器码,但是紧接着,sum 函数又传递了字符串类型,这就导致编译器只能讲刚编译好的 sum 函数拆解,然后重新将其编译为参数类型为字符串类型的机器码。这种情况大大降低了 JavaScript 的运行性能。

其次是因为编译器的一些优化策略可能“弄巧成拙”,导致在特定情况下性能反而有负面影响。一个典型的例子是 TypeScript 编译器在编译代码时的性能优化:

https://github.com/Microsoft/TypeScript/pull/10270

通过这个优化可以看到,通过将一个对象添加 delete 操作,强制关闭该对象的“隐藏类”机制,将一个对象切换为字典模式,达到了性能提升的作用。

再次是 JavaScript 具备垃圾回收机制,虽然 V8 编译器已经对垃圾回收机制的算法进行了诸多的优化,但是在应用内存占用较大时,垃圾回收的瞬间明显仍然还有卡顿现象,这导致了复杂应用有可能出现不定时的卡顿现象。

这些问题都反映出,JavaScript 这种语言机制本身的灵活性,反而限制了 JavaScript 引擎的性能优化空间,如果希望彻底解决这一问题,必然需要抛弃 JavaScript 这门语言本身,采用一门强类型的编程语言才能达到最极致的性能,在这种技术思想的指引下,WebAssembly 技术应运而生。

提到了 WebAssembly,就必然首先提及对其有深远影响的 asm.js,这是 Mozilla 在 2013 年推出的一项新技术,它是 JavaScript 的一个子集,舍弃了大量会导致性能问题的语法,并且被设计为通过 C / C++ 代码编译生成,而非手工编写 asm.js 代码。上述的 sum 函数在 asm.js 中表现为:

复制代码
function sum ( a ,b ) {
a = a | 0;
b = b | 0;
return ( a + b ) | 0;
}

上述代码中,标准的 JavaScript 引擎会对其进行解析,并生成正确的结果,而 asm.js 会根据一些不会对运行时造成计算结果错误的特殊标识对变量的类型进行声明(比如 a = a | 0 表示变量 a 是一个整数),通过这种方式,这种代码既可以在支持 asm.js 的 JavaScript 引擎上得到很高的性能,也会在不支持的设备上继续按照正确的逻辑进行执行,而非无法运行。

虽然如此,asm.js 仍然存在着一些问题,主要是基于 JavaScript 语法的文本格式解析速度不够快,并且代码尺寸偏大,为了解决这些问题,将 asm.js 进行二进制化的 WebAssembly 应运而生。

WebAssembly 是一种接近机器语言的跨平台二进制格式。2017 年 3 月份,四大主流浏览器厂商 Google Chrome、Apple Safari、Microsoft Edge 和 Mozilla FireFox 均宣布已经于最新版本的浏览器中支持了 WebAssembly 的初始版本,这意味着 WebAssembly 技术已经实际落地、可以在特定生产环境进行尝试。

WebAssembly 目前可以通过 Emscripten SDK 生成,下图是 WebAssembly 的编译原理:

上图展示了如何通过编写 C / C++ 代码生成 WebAssembly 内容。

首先通过 LLVM ,将 C/C++ 源代码编译为 LLVM bytecode。这 是一种跨语言的底层虚拟机字节码,理论上所有强类型编程语言均可以生成这种字节码。通过这一点可以得知,在未来理论上所有强类型编程语言(诸如 Java / C# 等)均可以开发 WebAssembly 程序。

其次,通过 EMScripten 中的后端编译器,将这种抽象字节码生成 asm.js 格式的文件。这是一种特殊的 JavaScript 代码,部分 JavaScript 引擎会将这种格式以比通常的 JavaScript 代码更快的速度运行,并且由于 asm.js 仍然是 JavaScript,所以哪怕 JavaScript 引擎不支持该特性,也会以通常的方式运行这段逻辑。这意味着使用 C/C++ 编写的源代码,哪怕用户设备不支持 WebAssembly,也可以回退到 JavaScript 运行并得到一致的结果。

第三,asm.js 会通过另一个编译器生成为 WebAssembly 的 .wasm 文件,由于 WebAssembly 是二进制格式,相比 JavaScript 而言,其代码体积同比小很多,并且由于已经是面向机器码的格式,也无需在运行前对源代码耗费时间进行 JIT 编译操作。

通过上述内容可以看出,WebAssembly 理论上可以通过任何强类型语言生成,不强制依赖用户的本地运行环境,代码体积小、解析速度快,几乎是 Web 开发未来的一颗“银色子弹”。

可惜的是在现阶段,WebAssembly 仍然存在着不少问题需要去解决。

首先是自身的稳定性,以 Chrome 浏览器为例,Chrome 57 支持 WebAssembly 的 MVP 版本,但是在 Chrome 58 上,大量的 WebAssembly 程序会直接导致进程崩溃,虽然后续的 Chrome 59 已经修复了绝大部分问题,但是仍然不得不对目前版本的稳定性持保留态度。

其次是可调试性,WebAssembly 被设计为了一种开放的、可调试的程序,但目前无论是 Chrome 还是 FireFox ,在调试方面还有很大的提升空间。由于在目前阶段调试较为困难,所以用 WebAssembly 编写业务逻辑代码对研发来说还是很不方便的。

还有就是与 Web 的互操作性。目前 WebAssembly 类似 WebWorker ,只能进行单纯的数值计算工作,不能在 C++ 层直接操作 DOM 节点。虽然在未来路线图中提及这一特性会在后续加入,但是在目前阶段 WebAssembly 更适合被用于更纯粹的密集型数据计算工作,而非直接编写业务逻辑。

综上所述,在目前阶段,WebAssembly 不适合直接编写具体的业务逻辑,而更适合编写应用程序中对性能要求比较高的库,并与 JavaScript 编写的业务逻辑进行通讯,并在 JavaScript 端对 DOM 节点进行操作。

以笔者最近开发的白鹭引擎 5.0 的渲染库为例,白鹭引擎对外提供 JavaScript API,开发者编写的 JavaScript 逻辑代码会汇总为一组命令队列发送给 WebAssembly 层,然后 WebAssembly 负责所有的计算工作,最终生成一组基于 WebGL 格式的数据流,最后 JavaScript 对这组数据流进行简单的解析并直接调用 DOM 的 WebGL 接口传递数据。

在实践过程中,我们总结出 WebAssembly 的几个不容易注意的优势和缺点:

  • 代码体积很小,我们将大约 300k 左右(压缩后)JavaScript 逻辑改用 WebAssembly 重写后,体积仅有 90k 左右。虽然使用 WebAssembly 需要引入一个 50k-100k 的 JavaScript 类库作为基础设施,但是总体来看资源尺寸的优势还是很大的。
  • 由于代码格式是二进制、无法直接在浏览器中看到源码,尽管理论上仍然可以通过逆向工程一定程度上得到原有的业务逻辑,但是由于开发者可以在编译时使用了 -O3 等激进的优化策略,所以最终反编译得到的业务逻辑也是很难阅读的。虽然理论上一切在客户端的内容都是不安全的,但是与所有代码都直接暴露给用户相比,代码安全性得到了很大的改善
  • 在运行 benchmark 等极限测试时,游戏引擎使用 WebAssembly 并不比 JavaScript 有几何量级的提升。笔者的推论是:由于 JavaScript 引擎的 JIT 机制会把经常运行的函数进行极限的编译优化,所以在 benchmark 这种代码大量反复执行的测试环境下,无论是 JavaScript 版本,还是 WebAssembly 版本,运行的都是高度优化后的机器码,虽然 WebAssembly 版本仍然比 JavaScript 版有一定的性能优势,但是并不明显。
  • 在运行业务逻辑代码时,由于大部分业务逻辑代码只运行一次,所以 JavaScript 引擎只会对这部分代码进行简单的编译优化而非极限优化,所以运行这一部分代码 WebAssembly 相比 JavaScript 版本而言提升巨大,但是因为上文所述,不建议开发者在编写业务逻辑时使用 WebAssembly,所以这里陷入了一个两难。在目前而言,理想情况是除了底层库之外,部分关键的涉及性能问题的逻辑也可以使用 WebAssembly 进行编写

综上所述,目前为止由于 WebAssembly 还不是非常完善,所以它目前的主要作用是作为 JavaScript 生态的有益补充,与 JavaScript 共存而不是取而代之。但是通过其路线图我们可以得知,WebAssembly 的设计思想非常优秀,目前所有存在的问题从长远的角度来说都是可以解决的问题。在加上 WebAssembly 是非常罕见的由四大浏览器厂商共同宣布会大力支持并实现的功能,其浏览器兼容性问题也终究可以得到解决,再退一步,哪怕旧式浏览器不支持,由于 WebAssembly 支持回退到 JavaScript,也可以保证正常运行。

在目前阶段,WebAssembly 适合大量密集计算、并且无需频繁与 JavaScript 及 DOM 进行数据通讯的场景。比如游戏渲染引擎、物理引擎、图像音频视频处理编辑、加密算法等。

笔者认为,WebAssembly 就像当初的 HTML5 标准一样,在公布之后最开始不被很多人看好,认为会有浏览器兼容性问题、各大浏览器厂商的实现问题、性能问题、用户需求与用户体验问题,但在近年来 HTML5 终于得到了广泛的使用,甚至有些人认为他可以在很多场景下取代 NativeApp ,而非仅仅是当年“取代 Flash”这一小目标。凭借着底层技术的跨越式发展,以及浏览器厂商的一致支持,WebAssembly 一定会有一个光明的未来,也许真的可以成为一颗 Web 开发的“银色子弹”。

作者介绍

王泽,白鹭引擎架构师

前端之巅关注「前端之巅」,紧跟前端发展,共享一线技术。各位淀粉投稿请发邮件到 editors@cn.infoq.com,注明“前端之巅投稿”。

2017-07-19 19:006733

评论

发布
暂无评论
发现更多内容

直播预告丨 立即解锁 ALB Ingress 高级特性

阿里巴巴云原生

阿里云 容器

VSCode一键接入Notebook体验算法套件快速完成水表读数

华为云开发者联盟

人工智能 华为云 企业号 2 月 PK 榜 华为云开发者联盟

4道数学题,求出极狐GitLab CI 流水线之最优解|第1题:有向无环图流水线

极狐GitLab

ci DevOps cicd pipeline 极狐GitLab

银斯微, W-Sharing取得TTA与PaaS-TA兼容级别1双项认证

科技热闻

渲染行业需要什么,云渲染的优势是什么?

Renderbus瑞云渲染农场

云渲染 云渲染农场 云渲染平台

区块链项目开发技术团队源码交付

开发微hkkf5566

2023年最新互联网大厂精选Java面试真题集锦(JVM、多线程、MQ、MyBatis、MySQL、Redis、微服务、分布式、ES、设计模式)

架构师之道

编程 程序员 计算机 java面试

什么是智能制造,为什么它对传统制造业影响如此之大?

PreMaint

智能工厂 智能制造

分布式缓存服务DCS:企业版性能更强,稳定性更高

华为云开发者联盟

云计算 后端 华为云 企业号 2 月 PK 榜 华为云开发者联盟

“采访”ChatGPT看看它对我们GreatSQL社区有什么看法

GreatSQL

MySQL greatsql greatsql社区

设计模式-策略模式详解

C++后台开发

设计模式 策略模式 后端开发 Linux服务器开发 C++开发

云原生技术在容器方面的应用

统信软件

容器 云原生 云服务

有了 ETL 数据神器 dbt,表数据秒变 NebulaGraph 中的图数据

NebulaGraph

数据库 大数据 数据处理 图数据库

从 await-to-js 到 try-run-js

jump-jump

JavaScript 异步 优化 Async 重试

陕西旅游集团旗下景区春节期间累计接待超200万人次,这背后也有火山引擎VeDI的身影

字节跳动数据平台

大数据 数据中台 字节跳动 数据产品

直播预告 | 数据库自治平台 KAP 监控告警架构及实例演示

KaiwuDB

监控告警 KaiwuDB 数据库自治

关于小游戏引擎你还了解哪些?

没有用户名丶

小程序游戏

智商狂飙,问了ChatGPT几个数据库问题后,我的眼镜掉了

NineData

人工智能 MySQL 数据库 ChatGPT NineData

记录一次还算优雅的代码设计

京东科技开发者

线程 cpu 优雅 代码设计 企业号 2 月 PK 榜

统一观测丨如何使用Prometheus 实现性能压测指标可观测

阿里巴巴云原生

阿里云 云原生 Prometheus 压测

高级java体系课第1期第二周作业

刘博

给 Databend 添加 Aggregate 函数 | 函数开发系例二

Databend

软件测试 | 项目管理与跨部门沟通协作

测吧(北京)科技有限公司

测试

Flink SQL 在米哈游的平台建设和应用实践

Apache Flink

大数据 flink 实时计算

明道云致几位重度抄袭者的公开信

明道云

直播预约|数据库掌门人论坛召开,共谋中国数据库生态发展新路径

镜舟科技

数据库 大数据 开源

从一个Demo说起Zookeeper服务端源码

宋小生

zookeeper

Apifox 1 月更新 | 将接口调试做到「极简」的新模式上线

Apifox

Apifox API

五大要点,让你掌握代码整洁之道!

SoFlu软件机器人

案例 | 在肯尼亚,青年们正在说着“Sheng”语...

澳鹏Appen

人工智能 nlp 数据标注 训练数据 小语种

软件测试 | 流程管理平台

测吧(北京)科技有限公司

测试

WebAssembly:解决 JavaScript 痼疾的银弹?_JavaScript_王泽_InfoQ精选文章