「譯」圖解 ArrayBuffers 和 SharedArrayBuffers

網易考拉前端團隊發表於2019-03-04

翻譯自:A cartoon intro to ArrayBuffers and SharedArrayBuffers

這是圖解 SharedArrayBuffers 系列的第二篇:

  1. 記憶體管理碰撞課程
  2. 圖解 ArrayBuffers 和 SharedArrayBuffers
  3. 用 Atomics 避免 SharedArrayBuffers 競爭條件

上一篇文章中,我解釋了 JavaScript 這類自動管理記憶體的語言是如何處理記憶體的,同樣也解釋了類似 C 語言這種手動管理記憶體的語言

為什麼這對於我們討論的 ArrayBuffersSharedArrayBuffers 如此重要?

因為即使你使用的是 JavaScript 這種自動管理記憶體的語言,ArrayBuffers 也提供了一種手動處理資料的途徑

為什麼你會有這樣的需求呢?

正如上篇文章所說,這裡有個權衡,自動管理記憶體對開發者是友好的,但是這會增加機器負擔,甚至會有效能問題

例如,JS 裡建立一個變數,引擎會去猜測變數的型別以及記憶體裡如何表示。因為有了型別猜測,JS 引擎通常會比真實需要預留更多的空間。根據變數不同,記憶體分配可能會是真實需求的 2-8 倍,這導致了記憶體浪費

而且,某些建立和使用 JS 物件的場景會讓垃圾回收變得很困難。如果你是手動維護的記憶體,可以根據實際使用需求來決定分配和釋放記憶體的策略

很多時候,這不是什麼大不了的事。大多數場景並不會對效能要求那麼苛刻,反而更多地擔心管理記憶體的麻煩。而且一般情況下,手動管理記憶體可能更慢

但是對於底層需要極致優化的場景,ArrayBuffers 和 SharedArrayBuffers 為你提供了可能

ArrayBuffer 是如何工作的

ArrayBuffer 跟其它 JavaScript 陣列差不多,但是不是所有 JavaScript 型別都可以放進去,比如物件、字串。你唯一可以放進去的只有位元組(可以用數字表示)

需要澄清的一點是,你事實上不是直接把這個位元組到 ArrayBuffer 裡就行了,ArrayBuffer 並不知道位元組有多長,該用多少位去存

ArrayBuffer 僅僅是一個個 0/1 組成的串,它不知道第一個元素和第二個元素的分割點

為了提供必要的上下文資訊,把 ArrayBuffer 分塊,我們需要把它包裹到檢視裡,這些資料的檢視可以通過帶型別的陣列新增,已經支援很多種型別的陣列了

例如,你可以用一個 Int8 型別的陣列把 0/1 串分割成 8 位一組的序列

或者你可以用一個無符的 Int16 型別陣列,把它分割成 16 位一組的序列,可以把它當作無符整型處理

甚至你可以在同一個基礎 buffer 上同時處理多種檢視,不同檢視在相同操作下會返回不同的結果

例如,如果我們從某個 ArrayBuffer 的 Int8 檢視得到第 0 和第 1 個元素的值,在 Uint16 檢視下,第 0 個元素與其有相同二進位制位值,但是得到的值也會不一樣

這種方式下,ArrayBuffer 幾乎是扮演原始記憶體角色了,它模擬記憶體的各種跟 C 語言裡類似的操作

你可能納悶了,為什麼不讓開發者直接操縱記憶體而是採用這個抽象層。因為直接操作記憶體會有安全風險,這個以後的文章會講

什麼是 SharedArrayBuffer

為了說明白 SharedArrayBuffers,我需要稍微解釋下並行執行程式碼和 JavaScript 的關係

為了更快執行程式碼或者更更快響應使用者事件,你可能會讓程式碼並行執行,為了做到這點,你需要分割工作

一個典型的應用中,所有的工作都由一個單獨的主執行緒處理,這點我之前提到過……這個主執行緒就像一個全棧工程師,掌管著 JavaScript、DOM 和 檢視

任何能夠從主執行緒負載減少工作的方法都對程式碼執行效率有幫助,某些情況下,ArrayBuffers 可以減少大量應該由主執行緒做的工作

但是也有些時候減少主執行緒負載是遠遠不夠的,有時你需要增援,你需要分割你的任務

大多數語言裡,這種分割工作的方法可以使用多執行緒實現,這就像很多人同時在一個專案裡工作。如果你可以完美地把任務分割為多個獨立的部分,你可以分給不同的執行緒,然後,這些執行緒就同時各種獨立執行這些任務

在 JavaScript 裡,你可以藉助 web worker 做這種事,這些 web workers 跟其它語言的執行緒還是有些區別的,預設它們不能共享記憶體

這意味著如果你想分配你的任務給別的執行緒,你需要完整把任務複製過去,這可以通過 postMessage 實現

postMessage 把你傳給它的任何物件都序列化,傳送到其它 web worker,然後那邊接收後反序列化並放進記憶體

這個過程是非常慢的

某些型別資料(如 ArrayBuffers)你可以通過移動記憶體的方式實現,這意味著把某個特定區域的記憶體移過去後其它 web worker 就可以直接訪問了

但是,之前的 web worker 就無法訪問了

對於某些場景這是實用的,但是也有很多場景對效能要求高,你只能使用共享的記憶體

而這就是 SharedArrayBuffers 為你提供的

有了 SharedArrayBuffer 後,多個 web worker 就可以同時讀寫同一塊記憶體了

你再也不需要 postMessage 伴有時延的通訊了,多個 web worker 對資料訪問都沒有時延了

當然,這種同時訪問也有風險,會產生競爭條件

這個下一篇文章會細說

SharedArrayBuffers 支援情況

所有主流瀏覽器都將會支援 SharedArrayBuffers

Safari 10.1 已經支援了,Firefox 和 Chrome 也會很快支援併發布,Edge 會在他們秋季 Windows 更新的時候釋出

即使所有主流瀏覽器都支援了,我們也不希望開發者直接使用它們,事實上,我們是反對的。你應該只使用更高階的封裝好的抽象層介面

我們期盼的是 JavaScript 庫開發者可以提供更簡單安全的方法來使用 SharedArrayBuffers

而且,一旦 SharedArrayBuffers 內建到平臺中,WebAssembly 可以通過它實現多執行緒,到那時候你就可以使用類似 Rust 的多執行緒語言輕鬆玩轉多執行緒了

下一篇文章我們會介紹一個為避免競爭條件的庫提供基礎操作的工具(Atomics

By Cody

相關文章