Wasm 初探,寫個 Hello World
大家好,我是前端西瓜哥。
我們來入門一下 wasm。
wasm 是什么
wasm 是 WebAssembly 的縮寫。
wasm 并不是傳統(tǒng)意義上匯編語言(Assembly),而是一種中間編譯的字節(jié)碼,可以在瀏覽器上運行非 JavaScript 語言,只要它能被編譯成 wasm。
wasm 的優(yōu)點:
- 可以使用 C/C++、Rust等語言編寫代碼,這個是 wasm 最大的價值所在;
- 高效快速,二進(jìn)制文件,以接近原生的速度運行;
- 安全,和 JS 有相同的沙盒環(huán)境和安全策略,比如同源策略;
- 絕大多數(shù)主流瀏覽器支持。另外可移植,非瀏覽器環(huán)境也能支持(塞個 v8 進(jìn)去,比如 nodejs);
- 使用其他語言的輪子。比如 Canvas 底層調(diào)用的 Skia C++ 庫,就通過 wasm 技術(shù)提供了一個名為 CanvasKit 的 NPM 包給開發(fā)者用 JS 開發(fā)。
缺點:
- 適用場景較少,適合 CPU 密集型的場景(比如 3D 渲染);
- 提升并沒有非常高(幾十倍),通??赡芫蛢扇兜臉幼??但對普通前端來說學(xué)習(xí)成本太高,還得看投入產(chǎn)出比;
- 和 JS 有通信的成本,通信頻繁或數(shù)據(jù)量大會降低性能。
安裝
首先我們需要用到 Emscripten。Emscripten 是一個編譯器工具鏈,使用 LLVM 去編譯出 wasm。
先安裝 Emscripten SDK。
我選擇官網(wǎng)推薦的方式進(jìn)行安裝。西瓜哥我用的系統(tǒng)是 MacOS.
# 拉取倉庫
git clone https://github.com/emscripten-core/emsdk.git
# 進(jìn)入目錄
cd emsdk
# 下載最新 SDK 工具
./emsdk install latest
# 版本設(shè)置為最新
./emsdk activate latest
# 將相關(guān)命令行工具加入到 PATH 環(huán)境變量中(臨時)
source ./emsdk_env.sh
下載那里我一開始失敗了幾次,后來用了程序員都懂的那個東西才下載成功。
看看是不是成功安裝了。
emcc -v
如果正確輸出版本相關(guān)信息,就是安裝成功了。
需要注意的是,每次打開新的終端,都要執(zhí)行一下 source ./emsdk_env.sh 去臨時更新 PATH 變量。
如果不想每次都要執(zhí)行這玩意,可以在 .zshrc(或 .bashrc)中加上:
# 需使用 emsdk_env.sh 文件的絕對路徑
source /path/to/emsdk_env.sh &> /dev/null
Hello World
接下來我們要選擇一門高級語言。
語言不能有 GC(自動垃圾回收機制)特性,比如 Java、Python。
(不過可以通過一些非官方的工具轉(zhuǎn)成 wasm,就是問題比較多)
寫 wasm,最流行的是 Rust 和 C/C++。
C/C++ 的輪子比較豐富,比如 Skia(Canvas 底層調(diào)用的庫)就是 C++ 寫的??上У氖?C/C++ 沒有包管理工具。
而當(dāng)下最炙手可熱的當(dāng)屬 Rust,我不得不說它真的很酷,有包管理工具,工具鏈也很完善。就是學(xué)習(xí)曲線過于陡峭,太難上手。
本文選擇使用 C/C++ 語言。
先創(chuàng)建一個 hello.c 文件:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
運行下面命令編譯成 wasm。
emcc hello.c
然后看到多了兩個文件:a.out.js 和 a.out.wasm。
其中 js 文件是膠水代碼,用來加載和執(zhí)行 wasm 的,wasm 不能直接作為入口文件使用。
我們用 nodejs 運行一下 a.out.js,可以看到成功輸出了 "Hello, world!"。
當(dāng)然我們也可以創(chuàng)建一個 html 文件,引入這個 a.out.js 文件,也可以看到控制臺能夠正確輸出輸出。
看下資源請求,可以看到 html 引入了 a.out.js,然后 a.out.js 再引入 a.out.wasm。
HTML 模板
為了方便大家調(diào)試,emscripten 還很貼心地提供了額外生成 index.html 的方式,并會引用上編譯出來的 js 文件。
我們需要不上 -o <文件名>.html 指定輸出的 html。
emcc hello.cpp -o hello.html
會生成 hello.html、hello.js 和 hello.wasm 三個文件。
打開 hello.html,我們可以看到一個界面,中間是一個 Canvas,顯示 wasm 的渲染結(jié)果。下面則是控制臺的輸出。
文件系統(tǒng)
出于安全考慮,wasm 最終是要在瀏覽器的沙箱內(nèi)運行的,是無法讀取本地文件的。
但我們還是可以使用 C++ 的讀取文件的方法的,只是它會被轉(zhuǎn)換為從虛擬文件系統(tǒng)里讀取。
hello_world_file.cpp 文件:
#include <stdio.h>
int main() {
FILE *file = fopen("./hello_world_file.txt", "rb");
if (!file) {
printf("cannot open file\n");
return 1;
}
while (!feof(file)) {
char c = fgetc(file);
if (c != EOF) {
putchar(c);
}
}
fclose (file);
printf("\n");
return 0;
}
需要讀取的文本文件 hello_world_file.txt 為:
==
This data has been read from a file.
The file is readable as if it were at the same location in the filesystem, including directories, as in the local filesystem where you compiled the source.
前端西瓜哥
==
使用 --preload-file 選項,指定要預(yù)加載的資源文件。
emcc hello_world_file.cpp -o hello.html --preload-file hello_world_file.txt
結(jié)果:
代碼優(yōu)化
和編譯 C 一樣,為了提高開發(fā)時的編譯效率,默認(rèn)編譯(默認(rèn)為 -O1)出來的 wasm 是沒有進(jìn)行優(yōu)化的。
下面命令會用優(yōu)化等級 2 進(jìn)行編譯。
emcc -O2 hello.c
還有其他的優(yōu)化等級,對應(yīng)編譯時間會變長,編譯出來的文件尺寸會變大。
結(jié)尾
本文簡單入門了一下 wasm。
wasm 是 JS 的補充,解決了 JS 的一些短板,不過總的來說大多數(shù)場景是用不上的,但它還在不斷發(fā)展,我還是挺看好的。未來可期。