网站建设 之 用js写wasm
为什么要这么做?编译js比解释js更快是必然的
wasm是什么?
我期望是一个二进制文件
WebAssembly(又名wasm)是一种高效的,低级别的编程语言。 它让我们能够使用JavaScript以外的语言(例如C,C ++,Rust或其他)编写程序,然后将其编译成WebAssembly,进而生成一个加载和执行速度非常快的Web应用程序。
WebAssembly被设计为针对Size和Load Time进行优化的格式,可以在各个硬件平台上以native speed运行;
安全性:WebAssembly是运行在沙盒内的,甚至可以和当前的Java虚拟机共享一套环境,并且也遵守浏览器各种跨域不跨域的规章制度;
开放性:WebAssembly开放标准,不受任何一家厂商控制,并且被设计为可以和Java API和Context交互
短期上 WASM 显然无法替代 JS——工具链调试困难、包体积庞大冗余、调 OpenGL 都要走回 JS 到 WebGL,性能未必有保证等。我们的团队踩过移植 C++ 原生渲染引擎的坑,这东西不深度掌握的话,目前只适合保证一个原生 App 在 Web 上原汁原味地凑合能用。要想做到好用,或是封装成完整的引擎级 API 在上层用 JS 深度开发,保证让人难受。我也不知道 WASM 为什么能这么巧妙地卡在原生团队和 Web 团队各自的边界之外,在完整从下到上搞 UI 的正经前端项目上(而不是音视频等特殊场景)让两边用起来都这么费劲。
V8 几乎是这个星球上最先进的工业级脚本语言引擎。别管你代码怎么梭,只要你能让 V8 走在 Happy Path 上,那妥妥都是原生级性能。
ES 规范在不断演化。这门语言向下兼容性极佳并且仍然在进化。技术圈内独家的转译玩法,让隔壁还有人为 2 还是 3 站队的时候,前端们都在用 Chrome 明年才会支持的语法特性了。
NPM 背后那个巨大的娱乐圈,哦不,社区支持。
V8 的技术路线很可能已经到达瓶颈了——不少数据显示,这条路线的综合性能上限,大约相当于纯原生应用的 1/20,而 WASM 能优化到原生的 1/4 左右。问题在于,大多数前端应用是事件驱动的,JS 完全不会 60fps 执行,甚至初级开发者还常常随手 setTimeout 几十毫秒而不影响体验。对一个 3Ghz 的 CPU 来说,在 50ms 内它就能执行 1.5 亿条指令。这个数字除以 JS 损耗的 20 倍,那也是 750 万条指令啊。
也就是说,gcc或者别的能直接编译出wasm

js解释器是v8,js编译器是
V8 执行一段 JavaScript 代码流程
生成抽象语法树(AST)和执行上下文
无论你使用的是解释型语言还是编译型语言,在编译过程中,它们都会生成一个 AST。这和渲染引擎将 HTML 格式文件转换为计算机可以理解的 DOM 树的情况类似。
第一阶段是分词(tokenize),又称为词法分析,其作用是将一行行的源码拆解成一个个 token。
第二阶段是解析(parse),又称为语法分析,其作用是将上一步生成的 token 数据,根据语法规则转为 AST。
将代码翻译成字节码(Bytecode)
由于直接执行机器码效率高,但是运行在 512M 内存的手机上,内存占用问题也暴露出来了,因为 V8 需要消耗大量的内存来存放转换后的机器码。为了解决内存占用问题,V8 团队大幅重构了引擎架构,引入字节码,并且抛弃了之前的编译器,
字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。
机器码所占用的空间远远超过了字节码,所以使用字节码可以减少系统的内存使用。
执行代码即时编译器(JIT)
在 Ignition 执行字节码的过程中,如果发现有热点代码(HotSpot),比如一段代码被重复执行多次,这种就称为热点代码,那么后台的编译器 TurboFan就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。
Java 和 Python 的虚拟机也都是基于即时编译(JIT)(字节码配合解释器和编译器)实现的。具体到 V8,就是指解释器 Ignition 在解释执行字节码的同时,收集代码信息,当它发现某一部分代码变热了之后,TurboFan 编译器就把热点的字节码转换为机器码,并把转换后的机器码保存起来,以备下次使用。
因此不用js写wasm的原因是:
js是嵌在html的,脱离html并没有其他应用场景,操作dom树没法用wasm
v8的即时编译功能,倘若你把一个纯数学的复杂函数封装成wasm,实际上v8会智能识别热点代码并编译成机器码,倘若你把一个纯数学的复杂函数封装成wasm恰恰代表你不清楚这个知识点,实际上所有的用js转wasm都是没必要的,除非说你有很多c/c++/java/python代码,不想再转成js,想让这些代码直接被js调用,那么可以编译成wasm是有点意义的,wasm的快的意义不大
React 的开发团队可以将他们的 reconciler 代码(即 Virtual DOM)替换成 WebAssembly 版本。使用 React 的人就什么都不用做 … 他们的应用程序会像之前一样正常运行,并因 WebAssembly 收益。
WebAssembly 由于下列因素执行更快:
拉取 WebAssembly 更节省时间。因为 WebAssembly 相比于 JavaScript 压缩率更高。
WebAssembly 解码 相比 解析 JavaScript 耗时更少。
编译和优化时间更短。因为 WebAssembly 相比 JavaScript 更贴近 机器码,而且 WebAssembly 在服务端已经做过了优化。
再优化 不再会发生。由于 WebAssembly 在编译时就已经内置了 类型等信息,所以 JS 引擎无需像对待 JavaScript 一样,在优化时推测类型。
更短的执行时间。因为开发者不需要为了更稳定的性能,去了解使用一些 有关编译器的 奇技淫巧,而且 WebAssembly 的指令集对机器更友好。
不依赖垃圾回收。因为内存是手动管理的。
实验性项目的确有人在做 从 jraphamorim/js2c 到 ballercat/walt ,基本思路都是解析js生成语法树然后接一个支持原生编译的后端。不过问题是一方面js这门语言太过灵活,而且语言本身的设计也不是为了效率,这样编译过去运行效率能提高多少很难说。另一方面真要做对性能要求很高的应用,在wasm或者在native平台上跑又面临c++、rust等语言的竞争。
所以至少目前来看,这类项目还是实验性质偏多,很难找到真正的应用价值。未来的话就看技术怎么发展,真要成熟了莫不是JS 引擎也会走一条类似 JVM -> Dalvik -> ART 的进化之路?
WASM 的确能把某些 JS 代码加速个几倍(其实大部分情况也就 1.x 倍),如果那些 JS 代码的原作者水平比较菜,那么用 cpp/rust 重写后,性能提升个十倍甚至九倍也是完全有可能的,然而由于 V8 实在太牛逼了,你费了一通老鼻子劲把一坨屎山迁到 WASM 之后,很有可能惊奇地发现它居然跑得更慢辣!
为什么呢?原因就在于 JS <-> WASM 的数据通信成本比想象中高很多,这其实是大多数需要跨语言通信的场景的瓶颈所在(比如一众小程序框架,以及 flutter 的 method channel 也有这种烦恼),尤其是当你需要把一个字符串丢进 WASM,然后再捞出来的时候,字符串拷来拷去是相当耗时的。
js 凭借强大的生态加上v8带来的优化,配合ts带来的类型检查 足以应付95%以上的业务场景。 wasm目前在前端看只要是解决了性能问题、私密代码编译加密;全局来看是提供了一个高性能统一的运行时(不区分前后端);单纯的从前端角度来看目前的确场景比较少,不过全局来看是有很多场景的,而且已经在落地使用了,比如envoy 利用wasm实现了高性能的动态扩展能力
如果你想写一个 高性能的xxx 工具用来替代市面上的 js 写的xxx 工具的话,你可能十有八九要失望了。只从 chrome 来说,因为 wasm 和 js 的字节码最后公用的 是同一个 优化编译器(turbo fan) 如果你的应用不是非常计算密集,加上js和 wasm 之间通信的额外消耗, 最后的性能可能和 js 写的应用差不多, 甚至比 js 版本还差,这个我有做过bench