WebAssembly的简单实践


浏览器的性能

JavaScript 一开始就是动态类型解释性语言,动态类型解释性语言的一大特点就是灵活和慢。

所以JavaScript 和所有的动态类型语言一样,天生会比静态类型语言慢。

而随着网页应用越来越复杂,JavaScript的运行性能就必须跟着提高。

那么为什么动态类型语言,如PythonPHPJavaScript就会比C/C++等静态类型语言慢呢?

JavaScript慢在哪里

我们来看一个最简单的情况,实现c = a + b加法运算,如果是C/C++语言,实现步骤大致如下:

  1. 内存a里取值到寄存器
  2. 内存b里取值到寄存器
  3. 算加法
  4. 把结果放到内存c

如果是JavaScript大致会经历哪些步骤呢?

  1. 代码编译
  2. 当前上下文是否有变量a,没有的话去上一层寻找,直到找到,或到达最外层上下文
  3. 当前上下文是否有变量b,没有的话去上一层寻找,直到找到,或到达最外层上下文
  4. 判断变量a的变量类型
  5. 判断变量b的变量类型
  6. 变量a、变量b是否需要进行类型转化,并进行类型转化
  7. 算加法
  8. 运行结果赋值给c

我们可以看到,二者的整个流程还是有比较大的区别

浏览器的性能填坑

1. JIT

JIT(just-in-time compilation):如果在执行c = a + b的时候,ab几乎都是int类型,那么是否可以去掉类型判断,类型转化的步骤,用接近C/C++的方式来实现加法运算,并把执行代码直接编译成机器码,直接运行,不需要再次编译。

Google 在 2009 年在 V8 中引入了 JIT 技术,JavaScript的执行速度瞬间提升了 20 - 40 倍的速度。
JIT的问题是并不是所有的代码都能得到很好的提升,因为JIT 基于运行期分析编译,而JavaScript是一个没有类型的语言,所以当代码中的类型经常变化的时候,性能提升是有限的。
比如

function add (a, b)
{
    return a + b
}
var c = add(1, 2);

JIT 看到这里, 觉得好开心, 马上把 add 编译成

function add (int a, int b)
{
    return a + b
}

但是,很有可能,后面的代码是这样的

var c = add("hello", "world");

JIT 编译器的可能当时就哭了,因为add已经编译成机器码了,只能推到重来

2. asm.js

2012年,Mozilla 的工程师 Alon Zakai 在研究LLVM编译器时突发奇想:许多 3D 游戏都是用 C / C++语言写的,如果能将 C / C++语言编译成 JavaScript 代码,它们不就能在浏览器里运行了吗?

于是,他开始研究怎么才能实现这个目标,为此专门做了一个编译器项目Emscripten。这个编译器可以将 C / C++代码编译成 JS代码,但不是普通的JS,而是一种叫做 asm.jsJavaScript变体。

asm.js它的变量一律都是静态类型,并且取消垃圾回收机制。当浏览器的JavaScript引擎发现运行的是 asm.js时,就会跳过语法分析这一步,将其转成汇编语言执行。asm.js的执行速度可以达到原生代码的50%

asm.js的一般工作流程为:

WeChat Screenshot_20210906143725.png

asm.js还是存在几个问题:

  1. 仅有FirFox的浏览器有良好的支持
  2. 代码传输还是与现有方式一样,传输源码,本地编译

3. WebAssembly

Mozilla,Google,Microsoft, 和Apple 觉得 Asm.js 这个方法有前途,想标准化一下,大家都能用。
便诞生了WebAssembly

有了大佬们的支持,WebAssemblyasm.js 要激进很多。 WebAssembly 连编译 JS 这种事情都懒得做了,不是要 AOT 吗? 我直接给字节码好不好?(后来改成 AST 树)。对于不支持 Web Assembly 的浏览器, 会有一段JavaScriptWeb Assembly 重新翻译为 JavaScript运行。

2019年12月5日,万维网联盟(W3C)宣布 WebAssembly成为正式标准

什么是WebAssembly

  • WebAssembly是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。
  • 它设计的目的是为诸如C、C++Rust等低级源语言提供一个高效的编译目标。

WebAssembly的目标

  • 高性能——能够以接近本地速度运行。
  • 可移植——能够在不同硬件平台和操作系统上运行。
  • 保密——WebAssembly是一门低阶语言,是一种紧凑的二进制格式,具有良好的保密性。
  • 安全——WebAssembly被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
  • 兼容——WebAssembly的设计原则是与其他网络技术和谐共处并保持向后兼容。

浏览器兼容性

兼容性

使用方法

官网有非常详细的使用说明
官网
MDN

1.安装依赖(Ubuntu 20 .04)

sudo apt install python3
sudo apt install cmake

2. 安装Emscripten

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

3. Hello World

创建一个文件hello.c:

#include <stdio.h>
int main() {
  printf("Hello, WebAssembly!\n");
  return 0;
}

编译C/C++代码:

emcc hello.c -s WASM=1 -o hello-wasm.html 

会生成三个文件:hello-wasm.html, hello-wasm.js, hello-wasm.wasm,然后浏览器打开hello-wasm.html,就能看到输出。

4. 调用C/C++函数

  1. 创建一个文件add.cpp:
extern "C" {

int add(int a, int b) {
  return a + b;
}

}
  1. 执行编译
emcc add.cpp -o add.html -s EXPORTED_FUNCTIONS='["_add"]' -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]'

这里的EXPORTED_FUNCTIONS参数指定需要暴露的函数接口名字,需要在名字前单独加一个下划线_EXPORTED_RUNTIME_METHODS指定可以被调用的方式

使用cwrap的方式调用,在生成的add.html中加入如下代码:

Module.onRuntimeInitialized = () => { 
    add = Module.cwrap('add', 'number', ['number', 'number']);
    result = add(9, 9);
}

因为调用对应的C/C++接口前,还需要先初始化,所以要在Module.onRuntimeInitialized事件后,才能通过JS调用C/C++的内容

5.测试性能

我们再实现一个JavaScript版本的加法函数

function js_add(a, b) {
    return a + b;
}

分别调用1000000次,对比分别的耗时,实现代码如下:

Module.onRuntimeInitialized = () => { 
    add = Module.cwrap('add', 'number', ['number', 'number']);
    const count = 1000000;
    let result;
        
    console.time("js call");
    for (let i = 0; i < count; i ++) {
        result = js_add(9, 9);
    }
    console.timeEnd("js call");
        
    console.time("c call");
    for (let i = 0; i < count; i ++) {
        result = add(9, 9);
    }
    console.timeEnd("c call");
}

大家觉得哪个更快?为什么?

现实可能和我们想象的不一样,在多次调用后,JavaScript的调用速度反而更快。

first_test.png

这是为什么呢?

其实是在我们多次调用JS函数时,由于多次调用输入,输出参数都是同样的类型,所以V8引擎会自动的优化我们的代码,

而我们调用WebAssembly的模块代码,中间的传输还需要一定时间,如果调用次数很多,中间的传输过程需要的时间就更多了,

所以会出现JavaScript的调用更快的情况。

6.另一个测试

这次换一个思路,直接在C中实现累加,修改上一步的add.cpp,并保存为add_all.cpp

extern "C" {

long add_all(int count) {
    long result = 0;
    for(int i = 0; i < count; i++){
        result += i;
    }
    return result;
}
}

用同样的命令进行编译

emcc add_all.cpp -o add_all.html -s EXPORTED_FUNCTIONS='["_add_all"]' -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]'

我们再实现一个JavaScript版本的js_add_all

function js_add_all(count) {
    let result = 0;
    for(let i = 0; i < count; i++){
        result += i;
    }
    return result;
}

然后进行运行测试:

Module.onRuntimeInitialized = () => { 
    add_all = Module.cwrap('add_all', 'number', ['number']);
    const count = 50000;
        
    console.time("js call");
    console.log(js_add_all(count));
    console.timeEnd("js call");
    
    console.time("c call");
    console.log(add_all(count));
    console.timeEnd("c call");      
}

这次谁更快?当count = 100000时会怎么样?为什么?

second_test.png

这次我们就可以看到明显的速度差异了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • WebAssembly 是一种可以使用非 JavaScript 编程语言编写代码并且能在浏览器上运行的技术方案。 ...
    hellomyshadow阅读 358评论 0 0
  • 上一篇文章分享了WebAssembly概念和基本使用,通过两个代码示例的分析对WebAssembly有了大致的了解...
    Netwarps阅读 1,807评论 0 0
  • 本次分享的文章是基于WebAssembly的探索与研究。最近需要做一个与加密相关的项目,想将后端的加密方案直接放到...
    Netwarps阅读 1,572评论 1 1
  • WebAssembly 系列(一)生动形象地介绍 WebAssemblyWebAssembly 系列(二)Java...
    合肥黑阅读 7,501评论 0 9
  • 1. 背景 WebAssembly是一种运行在现代网络浏览器中的新型代码并且提供新的性能特性和效果。它设计的目的不...
    何幻阅读 19,026评论 0 5