我們如何使用 WebAssembly 將我們的 Web 應用程序加速 20 倍(案例研究)
已發表: 2022-03-10如果您還沒有聽說過,這裡是 TL;DR:WebAssembly 是一種在瀏覽器中與 JavaScript 一起運行的新語言。 是的,這是對的。 JavaScript 不再是在瀏覽器中運行的唯一語言!
但除了“不是 JavaScript”之外,它的區別在於您可以將代碼從 C/C++/Rust(以及更多! )等語言編譯為 WebAssembly 並在瀏覽器中運行它們。 因為 WebAssembly 是靜態類型的,使用線性內存,並且以緊湊的二進制格式存儲,所以它也非常快,最終可以讓我們以“接近原生”的速度運行代碼,即接近你的速度。 d 通過在命令行上運行二進製文件來獲取。 利用現有工具和庫在瀏覽器中使用的能力以及相關的加速潛力是 WebAssembly 對 Web 如此引人注目的兩個原因。
到目前為止,WebAssembly 已用於各種應用程序,從遊戲(例如 Doom 3)到將桌面應用程序移植到 Web(例如 Autocad 和 Figma)。 它甚至可以在瀏覽器之外使用,例如作為一種高效靈活的無服務器計算語言。
本文是一個使用 WebAssembly 加速數據分析 Web 工具的案例研究。 為此,我們將使用一個用 C 編寫的現有工具來執行相同的計算,將其編譯為 WebAssembly,並用它來替換慢速 JavaScript 計算。
注意:本文深入探討了一些高級主題,例如編譯 C 代碼,但如果您沒有這方面的經驗,請不要擔心; 您仍然可以跟隨並了解 WebAssembly 的可能性。
背景
我們將使用的網絡應用程序是 fastq.bio,這是一個交互式網絡工具,可以讓科學家快速預覽其 DNA 測序數據的質量; 測序是我們讀取 DNA 樣本中“字母”(即核苷酸)的過程。
這是運行中的應用程序的屏幕截圖:
我們不會詳細介紹計算的細節,但簡而言之,上面的圖表讓科學家們了解測序的進展情況,並用於一目了然地識別數據質量問題。
儘管有數十種命令行工具可用於生成此類質量控制報告,但 fastq.bio 的目標是在不離開瀏覽器的情況下提供數據質量的交互式預覽。 這對於不熟悉命令行的科學家特別有用。
應用程序的輸入是由測序儀器輸出的純文本文件,其中包含 DNA 序列列表和 DNA 序列中每個核苷酸的質量分數。 該文件的格式稱為“FASTQ”,因此命名為 fastq.bio。
如果您對 FASTQ 格式感到好奇(不是理解本文所必需的),請查看 FASTQ 的 Wikipedia 頁面。 (警告: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 實現的架構相當簡單:
紅色框是我們進行字符串操作以生成指標的地方。 該框是應用程序中計算密集度更高的部分,這自然使其成為使用 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 之間的差異很小:
- 我們要求 Emscripten 生成一個
.wasm
和一個.js
來處理我們的 WebAssembly 模塊的實例化,而不是輸出二進製文件seqtk
- 為了支持 zlib 庫,我們使用標誌
USE_ZLIB
; zlib 非常常見,以至於它已經被移植到 WebAssembly,Emscripten 會為我們將它包含在我們的項目中 - 我們啟用 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 架構:
如圖所示,我們不是在瀏覽器的主線程中運行計算,而是使用 WebWorkers,它允許我們在後台線程中運行我們的計算,避免對瀏覽器的響應產生負面影響。 具體來說,WebWorker 控制器啟動 Worker 並管理與主線程的通信。 在 Worker 端,API 執行它收到的請求。
然後我們可以讓 Worker 對我們剛剛掛載的文件運行 seqtk 命令。 當 seqtk 完成運行時,Worker 通過 Promise 將結果發送回主線程。 一旦收到消息,主線程就會使用結果輸出來更新圖表。 與 JavaScript 版本類似,我們分塊處理文件並在每次迭代時更新可視化。
性能優化
為了評估使用 WebAssembly 是否有任何好處,我們使用每秒可以處理多少讀取的指標來比較 JavaScript 和 WebAssembly 的實現。 我們忽略了生成交互式圖形所花費的時間,因為這兩種實現都使用 JavaScript 來達到這個目的。
開箱即用,我們已經看到了約 9 倍的加速:
這已經很好了,因為它相對容易實現(那就是一旦你了解了 WebAssembly!)。
接下來,我們注意到雖然 seqtk 輸出了許多通常有用的 QC 指標,但其中許多指標並沒有被我們的應用程序實際使用或繪製成圖表。 通過刪除一些我們不需要的指標的輸出,我們能夠看到 13 倍的更大加速:
考慮到它是多麼容易實現,這又是一個很大的改進——通過逐字註釋掉不需要的 printf 語句。
最後,我們還研究了一項改進。 到目前為止,fastq.bio 獲取感興趣指標的方式是調用兩個不同的 C 函數,每個函數計算一組不同的指標。 具體來說,一個函數以直方圖的形式返回信息(即,我們將值分類為範圍的列表),而另一個函數返回作為 DNA 序列位置函數的信息。 不幸的是,這意味著同一個文件塊被讀取了兩次,這是不必要的。
因此,我們將這兩個函數的代碼合併為一個函數(儘管有些雜亂)(甚至不必復習我的 C 語言!)。 由於兩個輸出具有不同的列數,我們在 JavaScript 方面進行了一些爭論以解開兩者。 但這是值得的:這樣做讓我們實現了 20 倍以上的加速!
小心的話
現在是提出警告的好時機。 當你使用 WebAssembly 時,不要期望總能獲得 20 倍的加速。 您可能只能獲得 2 倍或 20% 的加速。 或者,如果您在內存中加載非常大的文件,或者需要在 WebAssembly 和 JavaScript 之間進行大量通信,您可能會變慢。
結論
簡而言之,我們已經看到,用調用已編譯的 WebAssembly 來替換慢速 JavaScript 計算可以顯著提高速度。 由於這些計算所需的代碼已經存在於 C 中,因此我們獲得了重用可信工具的額外好處。 正如我們還提到的,WebAssembly 並不總是適合這項工作的工具(喘氣! ),所以要明智地使用它。
延伸閱讀
- “使用 WebAssembly 升級”,Robert Aboukhalil
構建 WebAssembly 應用程序的實用指南。 - 蒜泥蛋黃醬(在 GitHub 上)
用於構建快速基因組學網絡工具的框架。 - fastq.bio 源代碼(在 GitHub 上)
用於 DNA 測序數據質量控制的交互式網絡工具。 - “WebAssembly 的簡短卡通介紹”,Lin Clark