天天看點

wasm 初探,寫個 Hello World

作者:前端西瓜哥

大家好,我是前端西瓜哥。

我們來入門一下 wasm。

wasm 是什麼

wasm 是 WebAssembly 的縮寫。

wasm 并不是傳統意義上彙編語言(Assembly),而是一種中間編譯的位元組碼,可以在浏覽器上運作非 JavaScript 語言,隻要它能被編譯成 wasm。

wasm 的優點:

  1. 可以使用 C/C++、Rust等語言編寫代碼,這個是 wasm 最大的價值所在;
  2. 高效快速,二進制檔案,以接近原生的速度運作;
  3. 安全,和 JS 有相同的沙盒環境和安全政策,比如同源政策;
  4. 絕大多數主流浏覽器支援。另外可移植,非浏覽器環境也能支援(塞個 v8 進去,比如 nodejs);
  5. 使用其他語言的輪子。比如 Canvas 底層調用的 Skia C++ 庫,就通過 wasm 技術提供了一個名為 CanvasKit 的 NPM 包給開發者用 JS 開發。

缺點:

  1. 适用場景較少,适合 CPU 密集型的場景(比如 3D 渲染);
  2. 提升并沒有非常高(幾十倍),通常可能就兩三倍的樣子?但對普通前端來說學習成本太高,還得看投入産出比;
  3. 和 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。

wasm 初探,寫個 Hello World

其中 js 檔案是膠水代碼,用來加載和執行 wasm 的,wasm 不能直接作為入口檔案使用。

我們用 nodejs 運作一下 a.out.js,可以看到成功輸出了 "Hello, world!"。

wasm 初探,寫個 Hello World

當然我們也可以建立一個 html 檔案,引入這個 a.out.js 檔案,也可以看到控制台能夠正确輸出輸出。

wasm 初探,寫個 Hello World

看下資源請求,可以看到 html 引入了 a.out.js,然後 a.out.js 再引入 a.out.wasm。

wasm 初探,寫個 Hello World

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 初探,寫個 Hello World

檔案系統

出于安全考慮,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
           

結果:

wasm 初探,寫個 Hello World

代碼優化

和編譯 C 一樣,為了提高開發時的編譯效率,預設編譯(預設為 -O1)出來的 wasm 是沒有進行優化的。

下面指令會用優化等級 2 進行編譯。

emcc -O2 hello.c
           

還有其他的優化等級,對應編譯時間會變長,編譯出來的檔案尺寸會變大。

結尾

本文簡單入門了一下 wasm。

wasm 是 JS 的補充,解決了 JS 的一些短闆,不過總的來說大多數場景是用不上的,但它還在不斷發展,我還是挺看好的。未來可期。

繼續閱讀