什么是wasm文件?
在使用浏览器控制台network的时候,有时会出现这样的请求:
没错,请求一个后缀名未wasm的文件,从控制台source中打开查看这个文件,发现这个文件看起来类似汇编一样的代码:
其实,这是通过WebAssembly技术生成的文件,本文介绍WebAssembly是什么。
什么是WebAssembly?
WebAssembly是一种可以将C/C++、Rust等高级语言编译成浏览器可执行的二进制文件的技术,他弥补了JavaScript性能低的缺点。可以说,WebAssembly技术出现的核心宗旨就是为了提升web应用程序的性能
JavaScript的执行过程
js代码的执行依赖js引擎,其执行过程大概分成以下几个阶段:
1、词法分析
这一步,程序员编写的代码会被分解成多个词法单元,词法单元包括关键字(如 function 、var )、标识符(如变量名和函数名)和操作符(如 + 、-、* 、/ 等)等。
举例,先写一个简单的js函数:
function add(x, y) {
return x + y;
}
这个函数被分解成词法单元后是下面这个样子:
Keyword 'function'
Identifier 'add'
Punctuator '('
Identifier 'x'
Punctuator ','
Identifier 'y'
Punctuator ')'
Punctuator '{'
Keyword 'return'
Identifier 'x'
Punctuator '+'
Identifier 'y'
Punctuator ';'
Punctuator '}'
2、语法分析
这一步,词法单元会被转化成抽象语法树(AST),通过树形结构表示js语法结构
举例,上面的函数生成的AST如下所示:
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "add"
},
"params": [
{
"type": "Identifier",
"name": "x"
},
{
"type": "Identifier",
"name": "y"
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Identifier",
"name": "x"
},
"right": {
"type": "Identifier",
"name": "y"
}
}
}
]
}
}
3、使用翻译器,将代码转化为字节码
4、使用字节码解释器,将字节码转化为机器码,最终运行的是机器码
为了提高运行速度,现代浏览器一般采用即时编译(JIT-Just In Time compiler),即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache);且js是有GC的,有GC就意味着在垃圾回收的时候会出现阻塞等待。js的这一系列特性决定了他难以完成高性能计算场景,例如游戏渲染、音视频渲染等。
但是许多厂商为web应用的性能提升做了许多努力,最具代表性的就是Adobe Flash Player和谷歌V8引擎。
提起Adobe Flash Player很多人的印象都很差,早期很多视频网站没有flash会无法播放,4399网站的许多小游戏也依赖flash,同时flash插件本身还有不少的广告;实际上,flash插件的最初设计目的为播放二维向量动画,后来被大量运用到网页游戏、动画中。乔布斯曾公开声明,永远不支持Adobe Flash,引发了Flash插件时代的终结。
谷歌V8引擎,算是一个重大的性能提升了,他跳过了生成字节码的过程,直接生成机器码。
WebAssembly的诞生
2015年,4个浏览器产商(Firefox, Chrome, Safari, Edge)达成合作,4个竞争者宣布联手开发WebAssembly。
2019年,W3C发布WebAssembly正式标准,WebAssembly成为继HTML、CSS、JavaScript之后第4种Web语言。
WebAssembly实际上属于一种编程语言,只不过他是二进制的,虽然提供了可以阅读的文本格式,但是依旧难以编写,直接编写WebAssembly文件等同于你在9102年的今天手写汇编,因此目前的WebAssembly文件都是通过C/C++等高级语言编译成的。值得注意的是,WebAssembly并不等同于机器码,他是一种低级二进制格式,也需要编译成机器可执行的机器码,只不过相比js的解析,它的编译过程更快更简单此外,此解码/编译步骤可以跨多个线程拆分,并且整个过程在模块仍在下载时开始。这大大减少了下载应用程序代码和达到峰值执行速度所需的时间。
但是WebAssembly并不能直接访问浏览器的API,例如DOM、WebGL、WebAudio等,他必须通过JavaScript代码来访问。
目前大部分浏览器都支持WebAssembly,开发人员可以在js代码中通过浏览器提供的API来调用WebAssembly函数,反之,WebAssembly也可以通过浏览器API调用js函数。
WebAssembly的应用
WebAssembly一般应用在需要高性能计算的场景上。目前部分web程序已经使用了这项技术,例如Google Earth( https://earth.google.com/web/)
WebAssembly的实际使用教程(Java版本)
事先声明,在写这篇博文的时候我虽然做过诸多尝试,但仍然没有成功将java代码转成WebAssembly,后来我放弃了,因为我觉得没有意义,WebAssembly是为了追求高性能而诞生的,所以理论上,应该选择从C/C++来编译,事实也确实如此,我找了一下java中和WebAssembly相关的生态,少的可怜,而且很不成熟,下面我介绍两个:
1、TeaVM,这是一个可以将jvm字节码文件编译成WebAssembly的编译器,只要是jvm字节码都可以,所以语言上可以选择java、scala、Kotlin等。这个是github地址: https://github.com/konsoletyper/teavm,官网文档地址: https://mirkosertic.github.io/Bytecoder/chapter-1/page-1-c/,文档简直依托答辩,不知道是哪个大聪明写的,看不懂好吧。
2、JWebAssembly,这是一个将java代码转成WebAssembly的库,github地址: https://github.com/i-net-software/JWebAssembly,文档地址: https://github.com/i-net-software/JWebAssembly/wiki,这个的文档写的比较好,容易懂,但是对maven的支持并不好,本人试了一遍并没有成功,各位读者可以尝试通过gradle打包。
无论通过什么方式编译WebAssembly,最后都会生成一个wasm文件,这个文件就是我们要的。下面介绍怎么在js代码中调用wasm。
浏览器提供了相关的API可以直接调用
fetch('example.wasm') //wasm文件path
.then(response => response.arrayBuffer()) //转成buffer数组
.then(bytes => WebAssembly.instantiate(bytes))
.then(module => {
const wasmFunc = module.instance.exports.myFunction;
const result = wasmFunc(1, 2); // 调用wasm中的函数,并传递参数 console.log(result); // 打印wasm函数返回的结果
});
这里有个demo可以看一下: https://i-net-software.github.io/JWebAssembly/samples/HelloWorld/HelloWorld.html,在浏览器控制台查看source
拓展阅读
推荐阅读一下这篇文章,对于直播类业务可提供参考价值: 爱奇艺直播WebAssembly优化之路
高谈阔论