1. 程式人生 > >【WebAssembly 的未來】成長技能樹(上)

【WebAssembly 的未來】成長技能樹(上)

人們對WebAssembly有些誤解。他們認為在2017年登陸瀏覽器的WebAssembly - 我們稱之為WebAssembly的最小可行產品(或MVP) - 是WebAssembly的最終版本。

我可以理解這類誤解來自何處。WebAssembly社群組實際上致力於後向相容。這意味著你今天所建立的WebAssembly將繼續在未來的某版瀏覽器上工作。

但這並不意味著WebAssembly是功能完整的。事實上,它遠非如此。WebAssembly將會有許多功能,它們將從根本上改變你使用WebAssembly所能做的事情。

我認為這些未來的功能有點像電子遊戲中的技能樹。我們已經完全掌握了這些技能中的前幾項,但我們還需要完善下面的整個技能樹來解鎖所有應用程式。

那麼讓我們看看已經存在的概念,然後我們就可以看到未來的將會有哪些新的東西。

WebAssembly故事的最開始於Emscripten,它可以通過將C ++程式碼轉譯為JavaScript實現了在Web上執行C++程式碼。這使得有可能將大量現有的C++程式碼庫(如遊戲和桌面應用程式)帶入到Web中。

但它自動生成的JS仍然比作為對比的原生程式碼慢得多。但Mozilla工程師發現了一個隱藏在所生成的JavaScript中的型別系統,並給出瞭如何使JavaScript執行得飛快的方法。這個JavaScript子集被命名為asm.js。

其他瀏覽器供應商看到asm.js的速度有多快之後,他們也開始向他們自己的引擎中新增同樣的優化處理。

但這並不是故事的結束。這只是一個開始。引擎自身可以做些事情使其執行更快。

但他們無法在JavaScript本身中做到這一點。因此,他們需要一種新的語言 - 專門為編譯而設計的語言。這就是WebAssembly。

那麼第一版WebAssembly需要哪些技能呢?我們需要什麼才能實現可以在網路上有效執行C和C++的最小可行產品呢?

技能: 編譯目標

使用WebAssembly的人知道他們不想僅支援C和C++。他們希望能夠將許多不同的語言編譯為WebAssembly。所以他們需要一個與語言無關的編譯目標。

他們需要類似組合語言這樣的東西,像桌面應用程式可被編譯成x86一樣。但這種組合語言不適用於實際的物理機。這將是一個概念機。

技能: 快速執行

編譯目標必須被設計,以便它可以非常快地執行。否則,在Web上執行的WebAssembly應用程式將無法滿足使用者對平滑互動和遊戲的期望。

技巧: 緊湊

除了執行時間,載入時間也需要快。 使用者對某些內容的載入速度有一定的期望。 對於桌面應用程式,由於應用程式已安裝在您的計算機上,因此它們會快速載入。 對於 Web 應用程式,因為 Web 應用程式通常不必載入與桌面應用程式一樣多的程式碼,使用者也期望載入很快。

但是,當你將這兩件事結合起來時,它會變得棘手。 桌面應用程式通常是相當大的程式碼庫。 因此,如果它們在網路上,當用戶第一次訪問 URL 時,需要下載和編譯很多內容。

為了滿足這些期望,我們需要我們的編譯器目標是緊湊的。 這樣,它可以快速通過網路傳輸。

技能:線性記憶體

除了執行時間,載入時間也需要快速。使用者對某些內容的載入速度有一定的期望。對於桌面應用程式來說,由於應用程式已安裝在你的計算機上,因此對它們的期望是快速載入。對於 Web 應用程式,期望也是載入時間能夠儘可能短,因為 Web 應用程式通常不必載入與桌面應用程式一樣多的程式碼。

但是,當你將這兩件事結合起來時,它會變得很棘手。桌面應用程式通常是相當龐大的程式碼庫。因此,如果它們位於網路上,當用戶第一次訪問此 URL 時,需要下載和編譯很多內容。

為了滿足這些預期,我們需要我們的編譯器目標是緊湊的。這樣,它可以快速通過網路進行傳輸。

技能:線性記憶體

這些語言還需要能夠以不同於 JavaScript 使用記憶體的方式使用記憶體。他們需要能夠直接管理他們的記憶體 —— 能區分出哪些位元組一起使用。

這是因為像 C 和 C++ 這樣的語言有一個叫做指標的底層特性。你可以擁有一個沒有值的,但具有該值的記憶體地址的變數。因此,如果你要支援指標,程式需要能夠從特定地址進行寫入和讀取。

但是,你無法實現從網上下載的程式可無限訪問記憶體中的位元組,任意訪問它們想訪問的地址。因此,為了建立一種對記憶體訪問的安全方式,就像使用原生程式做法一樣,我們必須建立一些可以對特定區域記憶體訪問的東西,並且別無它法。

為此,WebAssembly 使用線性記憶體模型。這是使用 TypedArrays 實現的。它基本上就像一個 JavaScript 陣列,除了這個陣列是僅包含位元組的記憶體。當你訪問其中的資料時,你只需使用陣列索引,你可以將其視為記憶體地址。這意味著你可以假裝這個陣列是 C++ 中的記憶體。

已解鎖成就

因此,使用所有這些技能,人們可以在瀏覽器中執行桌面應用程式和遊戲,就好像他們在其計算機上本機執行一樣。

這幾乎就是 WebAssembly 作為 MVP 釋出時的大致技能。它確實是MVP——最小可行產品。

它還支援某些型別的應用程式正常工作,但仍有許多其他技能可解鎖。

重量級桌面應用程式

下一個解鎖的成就是更重量級的桌面應用程式。

你能設想像 Photoshop 這樣的應用程式在你的瀏覽器中執行的場景嗎? 如果你可以像使用 Gmail 一樣在任何裝置上即時載入它呢?

我們已經開始見證這樣的事情。例如,Autodesk 的 AutoCAD 團隊已使其 CAD 軟體在瀏覽器中可用。Adobe 已經使用 WebAssembly 通過瀏覽器提供了 Lightroom。

但這也有一些我們還需要增加的功能,以確保所有這些應用程式 —— 即使是最最重量級的應用在瀏覽器中也能執行良好。

技能: 執行緒化

首先,我們需要支援多執行緒。現代計算機擁有多核。這些基本上是多個可並行處理問題的大腦。這可以使事情完成更快,但是為了使用這些核,你需要支援執行緒。

技能: SIMD

除了執行緒之外,還有另一種使用現代硬體的技術,它可以讓你並行處理事務。

那就是 SIMD:單指令多資料。使用 SIMD,可以佔用大塊記憶體,並切分到不同的執行單元,這類似於核心。然後你使用相同的程式碼 —— 在所有這些執行單元上執行相同的指令,但它們每個都將該指令應用於它們自己的資料位上。

技能: 64位定址

WebAssembly 需要充分利用的另一個硬體功能是64位定址。

記憶體地址僅僅是數字而已,所以如果你的記憶體地址長度只有32位,你只能擁有這麼多記憶體地址 —— 足夠4千兆位元組的線性記憶體。

但是對於64位定址,你有16E位元組記憶體。當然,你的計算機中沒有16E位元組的實際記憶體。因此,其最大值取決於系統實際可提供的記憶體大小。但這將可以對 WebAssembly 的地址空間進行人為限制。

技能: 流式編譯

對於這些應用程式,我們不僅需要它們快速執行。我們還需要其載入時間比目前更快。我們需要一些可用於提升載入時間的技能。

一個重要的步驟是進行流式編譯 —— 在下載期間仍在編譯 WebAssembly 檔案。WebAssembly 專門設計了用於實現輕鬆的流式編譯。在 Firefox 中,我們實際編譯它的速度比其通過網路傳入的速度快得多 —— 在下載檔案完成時,它幾乎完成了編譯。其他瀏覽器也在新增對流式編譯的支援。

另一件有用的事情是擁有一個分層編譯器。

對於我們在 Firefox 中,這意味著有兩個編譯器。第一個,基線編譯器 —— 在檔案開始下載後立即啟動。它可以非常快速地編譯程式碼,以便其快速啟動。

它所生成的程式碼執行速度很快,但並不是 100% 達到其最快速度。為了獲得額外的效能,我們在後臺的幾個執行緒上執行另一個編譯器 —— 優化編譯器。這個編譯需要更長的時間,但生成極快的程式碼。待其完成之後,我們將基線版本換成完全優化的版本。

這樣,我們可以使用基線編譯器實現快速啟動,並使用優化編譯器實現快速執行。

此外,我們正在開發一個名為 Cranelift 的全新的優化編譯器。Cranelift 旨在通過從函式層級上並行快速編譯程式碼。同時,它所生成的程式碼比我們當前的優化編譯器具有更好的效能。

Cranelift 目前處於 Firefox 的開發版本中,但預設情況下已被禁用。一旦我們啟用它,我們將更快地獲得完全優化後的程式碼,並且程式碼將執行得更快。

但是我們可以使用更好的技巧來實現之,所以我們大多數時間都不需要編譯了......

技能: 隱式 HTTP 快取

使用 WebAssembly,如果在兩個頁面載入時載入相同的程式碼,它將編譯為相同的機器程式碼。它不需要根據流經它的資料進行變化,就像 JS JIT 編譯器需要的那樣。

這意味著我們可以將編譯後的程式碼儲存在 HTTP 快取中。然後當頁面載入並轉到獲取 .wasm 檔案時,它將僅從快取中提取預編譯的機器程式碼。這會完全跳過了你已訪問過的快取中的任意頁面的編譯。

技能: 其他改進

目前許多討論正在圍繞其他改進思路以及避免更多工作,因此請繼續關注其他實時的改進資訊。

我們目前處於哪一階段?

我們目前支援哪些重量級應用程式?

執行緒化

關於執行緒化,我們已擁有一個做得不錯的提案,但其中一個核心部分 —— SharedArrayBuffers 不得不於今年早些時候在瀏覽器中關閉。
這將會被重新開啟。關閉它們只是一種臨時措施,以減少在 CPU 中發現的並於今年早些時候關閉的 Spectre 安全問題的影響,目前已取得進展,敬請期待。

SIMD

SIMD 目前處於非常活躍的開發階段。

64位定址

對 wasm-64,我們很好地瞭解瞭如何新增它,它與 x86 或 ARM 如何支援64位定址的實現非常類似。

流式編譯

我們在2017年底添加了流式編譯,並且其他瀏覽器也在為之努力中。

分層編譯

我們在2017年底同時添加了基線編譯器,並且其他瀏覽器在過去幾年中也已添加了同樣型別的架構。

隱式 HTTP 快取

在 Firefox 中,我們對於實現隱式 HTTP 快取的支援差不多接近收尾了。

即使這所有的功能仍在開發中,你會看到目前依然出現了一些重量級應用程式,因為 WebAssembly 已經為這些應用程式提供了所需的效能。

但是,一旦這些功能全部就位,這將是另一個已解鎖的成就,這樣更多類似的重量級應用程式將能夠在瀏覽器中使用。

但 WebAssembly 不僅適用於遊戲和重量級應用程式。它也適用於常規 Web 開發……對於那種 Web 開發人員習慣於:小模組型別的 Web 開發。

有時你的應用程式的一小部分會進行大量繁重的處理,並且在某些情況下,使用 WebAssembly 可以更快地完成此處理。我們希望將這些移植到 WebAssembly 中變得容易。

同樣,這是其中一些已經發生的案例。開發人員已經將 WebAssembly 模組整合到那些有諸多繁重工作的小模組上。

一個例子是源對映(source map)庫中的解析器,它在 Firefox 的 DevTools 和 webpack 中使用。它使用 Rust 重寫過,編譯為 WebAssembly,使其速度提高了11倍。並且在進行相同型別的重寫後,WordPress 的 Gutenberg 解析器平均速度提高了86倍。

但是為了讓這種用法真正普遍開來 —— 讓開發者這樣開發真正感到舒服,我們需要做更多的事情。