[譯] 用 WebAssembly 提速 Web App 20 倍(實例學習)

原文:How We Used WebAssembly To Speed Up Our Web App By 20X (Case Study)html

做者:www.smashingmagazine.com/contact/git

譯文:如何使用 WebAssembly 來提升咱們的 Web 應用的性能 20 倍(實例學習)github

譯者:Zavier Tangweb


概述:在這篇文章中,咱們將探索如何用編譯後的 WebAssembly 替代緩慢的 JavaScript 程序,從而提高 Web App 應用程序的速度。docker

若是你尚未據說過 WebAssembly,這是它的介紹:WebAssembly 是一種與 JavaScript 一塊兒運行在瀏覽器中的一種新的語言。沒錯!也就是說 JavaScript 再也不是惟一能在瀏覽器中運行的語言了!瀏覽器

可是,除了它與 JavaScript 的名稱不一樣以外,它的獨特之處在於,你能夠從 C / C++ / Rust 等更多的語言編譯成 WebAssembly,並在瀏覽器中運行它們。由於 WebAssembly 是靜態類型的,使用線性內存,並以較小的二進制格式存儲,因此它的運行速度很是快,可能接近本機原生程序的速度(即以接近於在命令行上運行二進制代碼的速度運行)。在瀏覽器中利用現有的工具和庫,以及相關的提速潛力,是 WebAssembly 如此強大的兩個緣由。bash

到目前爲止,WebAssembly 已被用於各類應用程序,從遊戲(如 毀滅戰士 3)到將桌面應用程序移植到 Web (如 AutocadFigma。它甚至能夠在瀏覽器以外使用,例如能夠做爲一種用於 serverless computing 的高效且靈活的語言。網絡

本文是一個使用 WebAssembly 加速數據分析 Web 工具的案例。爲此,咱們將使用一個用 C 語言編寫的能夠執行相同計算的程序,將其編譯爲 WebAssembly,使用它來替換緩慢的 JavaScript架構

注意:本文深刻研究了一些高級內容,好比編譯 C 代碼。可是若是你沒有這方面的經驗,請不要擔憂,你仍然可以一塊兒來了解 WebAssembly 的功能。app

背景介紹

咱們將使用的 Web 應用程序是 fastq.bio,它是一個交互式網絡工具,爲科學家提供快速預覽 DNA 測序數據的質量。測序是咱們讀取 DNA 樣本中的 「字母」 (即核苷酸)的過程。

下面是應用程序的截圖(查看大圖):

Interactive plots showing the user metrics for assessing the quality of their data

咱們不會詳細地討論計算的細節,可是簡單地說,上圖是爲科學家提供了排序進行過程的可視化交換體驗,並被用於快速識別數據質量。

儘管有許多的命令行工具能夠生成這樣的質量控制報告,可是 fastq 的目標是,在不離開瀏覽器的狀況下提供數據質量的交互式預覽。對於不熟悉命令行的科學家來講,這很是有用。

應用程序的輸入是一個純文本文件,由測序儀輸出,包含 DNA 序列列表和 DNA 序列中每一個核苷酸的質量分數。該文件的格式稱爲 「FASTQ」,所以這個工具被叫作 fastq.bio

若是您對 「FASTQ」 格式感興趣,請查看 Wikipedia page 瞭解更多。

用 JavaScript 實現 Fastq.Bio

在 fastq.bio 的原始版本中,用戶首先從計算機中選擇一個 FASTQ 文件。使用 File 對象,應用程序從隨機的字節位置開始讀取一小塊數據(使用 FileReader API)。在該數據塊中,咱們使用 JavaScript 執行基本的字符串操做並計算相關指標。其中一個度量標準幫助咱們跟蹤咱們一般在 DNA 片斷的每一個位置上看到的 A、C、G 和 T 的數量。

一旦爲該數據塊計算了度量標準,咱們就會用 Plotly.js 交互式地繪製結果,而後繼續處理文件中的下一個塊。將文件分紅小塊處理的緣由僅僅是爲了改進用戶體驗:一次處理整個文件將花費太長的時間,由於 FASTQ 文件一般是幾百 GB 的。咱們發現,0.5 MB 到 1 MB 之間的塊大小將使應用程序的計算更加完美,並將更快地向用戶返回信息,可是這個數字大小會根據應用程序的細節和計算量的大小而變化。

咱們最初的 JavaScript 實現的架構至關簡單:

Randomly sample from the input file, calculate metrics using JavaScript, plot the results, and loop around

fastq.bio 的 JavaScript 實現架構。(查看大圖)

紅色的框是咱們進行字符串操做以生成度量的地方。該框是應用程序中計算密集型的部分,這天然使它成爲使用 WebAssembly 進行運行時優化的目標。

用 WebAssembly 實現 Fastq.Bio

爲了探索咱們是否能夠利用 WebAssembly 來加速咱們的 Web 應用程序,咱們搜索了一個現成的工具來計算 FASTQ 文件上的 QC 指標。具體地說,咱們尋找了一個用 C / C++ / Rust 編寫的工具,這樣它就能夠移植到 WebAssembly,並且這個工具已經獲得了科學界的驗證和信任。

通過一些研究,咱們決定使用 seqtk,這是一個用 C 語言編寫的經常使用的開源工具,能夠幫助咱們評估測序數據的質量(一般用於操做這些數據文件)。

在編譯到 WebAssembly 以前,讓咱們首先考慮如何將 seqtk 編譯爲二進制文件,以便在命令行上運行它。根據生成文件,你須要執行如下 gcc 命令:

# Compile to binary
$ gcc seqtk.c \
   -o seqtk \
   -O2 \
   -lm \
   -lz
複製代碼

另外一方面,要將 seqtk 編譯到 WebAssembly,咱們可使用 Emscripten toolchain,它爲現有的構建工具提供了替換,從而使得在 WebAssembly 中工做更加容易。若是你沒有安裝 Emscripten,你能夠下載咱們在 Dockerhub 上準備的 docker 鏡像,裏面有你須要的工具(你也能夠從頭開始安裝,但一般須要一段時間):

$ docker pull robertaboukhalil/emsdk:1.38.26
$ docker run -dt --name wasm-seqtk robertaboukhalil/emsdk:1.38.26
複製代碼

在容器內部,咱們可使用 emcc 編譯器代替 gcc

# Compile to WebAssembly
$ emcc seqtk.c \
    -o seqtk.js \
    -O2 \
    -lm \
    -s USE_ZLIB=1 \
    -s FORCE_FILESYSTEM=1
複製代碼

如你所見,編譯爲二進制和 WebAssembly 相比差別是很是小的:

  1. 咱們要求 Emscripten 生成一個 .wasm.js 來處理 WebAssembly 模塊的實例化,而不是輸出二進制文件 seqtk
  2. 爲了支持 zlib 庫,咱們使用 USE_ZLIB 標誌;zlib 很是常見,它已經被移植到 WebAssembly,Emscripten 將在咱們的項目中包含它
  3. 咱們啓用了 Emscripten 的虛擬文件系統,這是一個相似 POSIX 的文件系統(這裏是源碼),可是它在瀏覽器的 RAM 中運行,當你刷新頁面時就會消失(除非你使用 IndexedDB 將其狀態保存在瀏覽器中,但這應該放在另外一篇文章中討論)。

爲何是虛擬文件系統?爲了回答這個問題,讓咱們比較一下如何在命令行上調用 seqtk 和使用 JavaScript 調用編譯後的 WebAssembly 模塊:

# On the command line
$ ./seqtk fqchk data.fastq

# In the browser console
> Module.callMain(["fqchk", "data.fastq"])
複製代碼

訪問虛擬文件系統很是強大,由於這意味着咱們沒必要重寫 seqtk 來處理字符串輸入。咱們能夠在虛擬文件系統上掛載一組數據做爲文件 data.fastq,並簡單地調用 seqtk 的 main() 函數。

將 seqtk 編譯到 WebAssembly 後,下面是新的 fastq.bio:

Randomly sample from the input file, calculate metrics within a WebWorker using WebAssembly, plot the results, and loop around

WebAssembly + WebWorkers 實現的 fastq.bio(查看大圖

如圖所示,咱們沒有在瀏覽器的主線程中運行計算,而是使用 WebWorkers,它容許咱們在後臺線程中運行計算,從而避免對瀏覽器的響應性產生負面影響。具體來講,WebWorker 控制器啓動 Worker 並管理與主線程的通訊。在 Wroker 的一端,使用相關 API 執行它接收到的請求。

而後,咱們能夠要求 Worker 對剛剛掛載的文件運行 seqtk 命令。當 seqtk 運行完畢時,Worker 經過一個 Promise 將結果發送回主線程。一旦接收到消息,主線程就將結果輸出來更新圖表。與 JavaScript 版本相似,咱們以塊的形式處理文件,並在每次迭代中更新可視化視圖。

優化

爲了評估使用 WebAssembly 是否有任何好處,咱們使用每秒能夠處理多少讀取數據來比較 JavaScript 和 WebAssembly。咱們忽略了生成交互式圖形所花費的時間,由於這兩種實現方法都使用 JavaScript 來實現此目的。

開箱即見,有大約 9 倍的加速:

Bar chart showing that we can process 9X more lines per second

使用 WebAssembly,咱們能夠看到與最初的 JavaScript 實現相比,速度提升了 9 倍。(查看大圖

這已經很是好了,由於它相對來講比較容易實現(只須要你理解了 WebAssembly 便可)。

接下來,咱們注意到,雖然 seqtk 輸出了不少一般有用的 QC 指標,但咱們的應用程序實際上並無使用或繪製這些指標。經過刪除一些咱們不須要的指標的輸出,能夠看到一個更大的速度,13 倍:

Bar chart showing that we can process 13X more lines per second

刪除沒必要要的輸出能夠進一步提升性能。(查看大圖

這又是一個很大的改進,由於很容易實現,經過註釋掉不須要的輸出語句。

最後,咱們研究了另外一個改進。到目前爲止,fastq.bio 得到相關度量的方法是經過兩個不一樣的 C 函數,每一個函數計算一組不一樣的度量。具體來講,一個函數以直方圖的形式返回信息(即咱們放入範圍的值列表),而另外一個函數以 DNA 序列位置的函數返回信息。不幸的是,這意味着相同的文件塊被讀取兩次,這是沒必要要的。

所以,咱們將這兩個函數的代碼合併到一個函數中(儘管有些混亂)。因爲這兩個輸出的列數不一樣,因此咱們在 JavaScript 端進行了一些區分,以便將它們分開。但這是值得的:這樣作讓咱們實現了大於 20 倍的加速!

Bar chart showing that we can process 21X more lines per second

最後,使代碼只讀取每一個文件塊一次能夠提升大於 20 倍的性能。(查看大圖

注意

如今是提出警告的好時機。當你使用 WebAssembly 時,不要老是指望獲得 20 倍的加速。你可能只獲得 2 倍的加速或者 20% 的加速。若是在內存中加載很是大的文件,或者須要在 WebAssembly 和 JavaScript 之間進行大量通訊,那麼速度可能會變慢。

總結

簡而言之,咱們已經看到用調用編譯後的 WebAssembly 來替代緩慢的 JavaScript 程序能夠顯著提升速度。因爲這些計算所需的代碼已經用 C 語言實現過了,因此咱們得到了重用可信工具的額外好處。正如咱們還提到的, WebAssembly 並不老是適合這項工做的工具,因此要明智地使用它。

延伸閱讀


譯者注:《JavaScript Weekly》週刊正在翻譯中...

請戳 -> JavaScript-Weekly-zh-CN

相關文章
相關標籤/搜索