用 WebAssembly 爲 Web 應用提速20倍!(案例研究)

翻譯:瘋狂的技術宅
原文: https://www.smashingmagazine....

本文首發微信公衆號:前端先鋒
歡迎關注,天天都給你推送新鮮的前端技術文章html


在本文中,咱們將探討如何經過用已編譯的 WebAssembly 替換 JavaScript 來加速 Web 應用。前端

若是你還有據說過 WebAssembly,就先看一下解釋:WebAssembly 是一種在瀏覽器中與 JavaScript 一塊兒運行的新語言。沒錯, JavaScript 再也不是惟一在瀏覽器中運行的語言了!git

除了「不是 JavaScript」以外,最大的區別是你能夠將 C/C++/Rust(甚至更多!)等語言的代碼編譯爲 WebAssembly 並在瀏覽器中運行。由於 WebAssembly 是靜態類型的,使用線性內存並以緊湊的二進制格式存儲,因此它很是快,最終可讓咱們以「接近原生」的速度運行代碼,即速度接近你經過運行二進制文件達到的速度。可以利用現有工具和庫在瀏覽器中使用的能力以及在運行速度上的潛力,是 WebAssembly 引人注目的兩個緣由。程序員

到目前爲止,WebAssembly 已被用於各類應用,從遊戲(例如Doom 3)到把桌面程序移植到 Web(例如AutocadFigma )。它甚至能夠在瀏覽器以外使用,例如 serverless 高效計算github

本文是一篇用 WebAssembly 對 Web 數據分析工具進行加速的研究性案例。爲此咱們用 C 編寫的已有工具執行相同的計算,並將其編譯爲 WebAssembly 來替換慢速的 JavaScript 計算。web

注意本文深刻研究了一些高級主題,好比編譯 C 代碼,但若是你沒有相關經驗,請不要擔憂;你仍然能夠繼續瞭解使用 WebAssembly 的可行性。面試

背景

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

這是程序的截圖:segmentfault

用交互式圖表顯示針對用戶指標數據的評估質量

咱們不會詳細討論關於計算的東西,但簡而言之,上面的圖表讓科學家們瞭解了測序的進展狀況,並可以一目瞭然地對數據的質量進行檢查。瀏覽器

儘管許多命令行工具都可以生成這類質量控制報告,但 fastq.bio 的目標是在瀏覽器中提供數據質量的交互式預覽。這對於不熟悉命令行的科學家特別有用。

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

若是你對 FASTQ 格式感到好奇,請查看FASTQ的維基百科頁面。 (警告:FASTQ文件格式可能會令你不忍直視。)

Fastq.Bio:JavaScript 實現

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

一旦該數據塊的度量標準計算完畢,咱們將用 Plotly.js 以交互方式繪製結果,而後再轉到文件中的下一個塊。以小塊處理文件的緣由只是爲了改善用戶體驗:一次處理整個文件須要太長時間,由於 FASTQ 文件一般有幾百 GB。咱們發現 0.5 MB 到 1 MB 之間的塊大小會使程序運行得更加流程,而且能夠更及時地向用戶返回信息,可是這個數字會根據程序的具體狀況和計算量的大小有所不一樣。

咱們最開始用 JavaScript 實現的架構很是簡單:

fastq.bio 用 JavaScript 實現的體系結構:從輸入文件中隨機抽樣,用 JavaScript 計算指標並繪製結果,而後循環
fastq.bio 用 JavaScript 實現的體系結構:從輸入文件中隨機抽樣,用 JavaScript 計算指標並繪製結果,而後循環

紅色方框是進行字符串操做以生成指標的地方。該框是程序中計算密集度最高的那一部分,很顯然應該用 WebAssembly 對其進行運行時優化。

Fastq.Bio:WebAssembly 實現

爲了探索是否能夠利用 WebAssembly 來加速 Web 應用,咱們搜索了一個現成的工具來計算 FASTQ 文件的 QC 指標。具體來講,咱們須要找一個用C/C++/Rust 編寫的而且已經被科學界驗證和信任得工具,而後把它移植到 WebAssembly。

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

在將其編譯到 WebAssembly 以前,先讓咱們研究一下怎樣將 seqtk 正常編譯爲二進制文件以便在命令行上運行。經過研究 Makefile,找到了用 gcc 進行編譯的命令:

# Compile to binary
$ gcc seqtk.c \
   -o seqtk \
   -O2 \
   -lm \
   -lz

另外一方面,爲了將 seqtk 編譯爲 WebAssembly,咱們須要用到 Emscripten 工具鏈,它能夠直接替換現有的構建工具,使編譯 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 模塊這兩種方式:

# 在命令行調用
$ ./seqtk fqchk data.fastq

# 在瀏覽器控制檯中調用
> Module.callMain(["fqchk", "data.fastq"])

虛擬文件系統很是強大,由於這意味着沒必要爲了處理輸入參數而重寫 seqtk 。咱們能夠將一塊數據做爲文件 data.fastq 掛載到虛擬文件系統上,而後簡單地調用 seqtk 的 main()函數便可。

將 seqtk 編譯爲 WebAssembly 後,獲得了新的 fastq.bio 架構:

webAssembly 的體系結構和 fastW.bio 的 WebWorkers 實現:在輸入文件中隨機抽樣,用 WebAssembly 在WebWorker 中計算指標,繪製結果並循環

webAssembly 的體系結構和 fastW.bio 的 WebWorkers 實現:在輸入文件中隨機抽樣,用 WebAssembly 在WebWorker 中計算指標,繪製結果並循環

如圖所示,不用瀏覽器主線程而是用 WebWorkers ,這樣能夠在後臺線程中執行咱們的計算,並避免對瀏覽器的響應性產生負面影響。具體來講,WebWorker 控制器啓動 Worker 並管理與主線程的通訊。對於 Worker,API 執行它收到的請求。

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

性能優化

爲了評估 WebAssembly 是否真的可以提升運行效率,咱們用每秒讀取並處理的數量做爲度量指標來比較 JavaScript 和 WebAssembly 兩種實現。在這裏忽略了生成交互式圖表所需的時間,由於兩種實現都用了 JavaScript 來達到這一目的。

開箱即用,能夠看到速度大約提高了 9 倍:

WebAssembly 與 JavaScript 實現相比,速度提高了 9 倍

WebAssembly 與 JavaScript 實現相比,速度提高了 9 倍

這樣已經很好了,由於它相對容易實現(前提是你理解了 WebAssembly!)。

接下來,咱們注意到雖然 seqtk 輸出了許多有用的QC指標,但程序實際上並未使用或繪製了這些指標。經過剔除不須要的指標輸出,能夠看到速度提升 13 倍:

刪除沒必要要的輸出能夠進一步提升性能。

刪除沒必要要的輸出能夠進一步提升性能。

實現它是多麼的容易,這又是一個很大的改進。

最後,咱們還會進一步改進。到目前爲止,fastq.bio 經過調用兩個不一樣的C函數來獲取感興趣的指標,每一個函數計算一組不一樣的指標。具體作法是一個函數以直方圖的形式返回信息(即被列入範圍的值的列表),而另外一個函數返回 DNA 序列位置的信息。不幸的是這意味着同一塊文件被讀取了兩次,這是沒有必要的。

因此咱們把這兩個函數的代碼合併爲一個(能夠不用去修改 C 代碼!)。因爲兩個輸出的列數不一樣,咱們在 JavaScript 這邊作了一些重構。這是值得的:可讓咱們獲得 20 倍的速度提高!

最後,對代碼進行重構,使每一個文件塊只讀取一次,這使咱們的性能提升了21倍最後,對代碼進行重構,使每一個文件塊只讀取一次,這使咱們的性能提升了21倍。

當心

使用 WebAssembly 時,不要指望老是得到 20 倍的加速。若是在內存中加載很是大的文件,或者須要在 WebAssembly 和 JavaScript 之間進行大量通訊,則可能會變慢。你可能只會獲得 2 倍甚至是 20% 的速度。

結論

咱們已經看到,經過調用編譯的 WebAssembly 來替換 JavaScript 可使處理速度顯著增長。因爲這些計算所需的代碼已經存在於 C 中,所以咱們獲得了重用可信工具帶來的額外好處。正如前面所提到的,WebAssembly 並不老是適合這種工做,因此還須要明智地去使用它。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:

相關文章
相關標籤/搜索