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