iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0

昨天我們瞭解到使用 postMessage 時,隨著傳遞的資料越大,耗費的時間就會越久,雖然在桌機上沒什麼問題,但到了手機這種低硬體的環境上,就可能會有效能問題了,此時有另一個可以大幅改善效能的方式就是使用 Transferable objects

Transferable objects 型態的資料可以在不同的線程中進行 轉移(transfer),被轉移的資料就不會套用 postMessage 預設的 structuredClone 算法把資料複製後再傳遞給另一個線程,而是類似 傳遞引用(reference) 的方式,將原本在主線程資料的所有權轉移到 worker 線程中,因為略過資料複製的步驟,所以可以大幅提高效能。

在 postMessage 中傳遞 Transferable objects

Transferable objects 代入到 postMessage 中的第二個參數就可以將資料轉移到 worker 線程中,下面創建了一個 Uint8Array 型態的資料,並將 uInt8Array.buffer 的資料轉移給 worker 線程。

// 創建 Transferable objects (Uint8Array 資料型態)
const uInt8Array = new Uint8Array(1024 * 1).map((v, i) => i);

// 將資料轉移到 worker 線程
worker.postMessage(uInt8Array, [uInt8Array.buffer]);

而為了避免資料同步問題,Transferable objects 從一個線程轉移到另一個線程之後,原本的那個線程就不能再操控這筆資料了,因此上述例子的 uInt8Array.byteLength 在轉移過後佔用的記憶體大小會變為 0。

// 創建 1024 bytes 大小的 uInt8Array
const uInt8Array = new Uint8Array(1024 * 1).map((v, i) => i);
console.log(uInt8Array.byteLength); // 1024

worker.postMessage(uInt8Array, [uInt8Array.buffer]);
// 轉移過後資料就不在原本的線程了,所以 byteLength === 0
console.log(uInt8Array.byteLength); // 0

另外並不是所有 Javascript 的型別都是可以 Transferable 的,像以上例子,可以被轉移的型別是
ArrayBuffer (uInt8Array.buffer),如果這裡不小心寫成用 uInt8Array 轉移的話就會收到錯誤說這個參數不是可轉移的資料 Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': Value at index 0 does not have a transferable type.

// 丟入不可轉移型態的資料會噴錯
worker.postMessage(uInt8Array, [uInt8Array]);

在 structuredClone 中傳遞 Transferable objects

之前提到的 structuredClone 函式同樣可以傳遞 Transferable objects

// 創建 1024 bytes 大小的 uInt8Array
const uInt8Array = new Uint8Array(1024 * 1).map((v, i) => i);
console.log(uInt8Array.byteLength); // 1024

const transferred = structuredClone(uInt8Array, {
  transfer: [uInt8Array.buffer],
});
console.log(uInt8Array.byteLength); // 0
console.log(transferred.byteLength); // 1024

測試 Transferable objects 效能

Demo
我們將昨天的範例程式稍微改動,增加傳遞 transfer 的參數,就可以比較有無開啟 transfer 之間傳遞速度的差異:

const sendMessage = async (data) => {
  return new Promise((resolve) => {
    if (isTransfer) {
      // 將 buffer 資料轉移到 worker
      worker.postMessage(data, [data.buffer]);
    } else {
      // 用預設的 structuredClone 算法
      worker.postMessage(data);
    }

    worker.onmessage = (e) => {
      resolve(e.data);
    };
  });
};

沒有傳遞 transfer 參數,使用 structuredClone 複製
https://ithelp.ithome.com.tw/upload/images/20230921/20162687dw8Knjtjhg.png
有傳遞 transfer 參數
https://ithelp.ithome.com.tw/upload/images/20230921/201626872O9qVVVWdZ.png

可以看到傳遞 transfer 參數時,所需時間大幅降低了,特別是檔案越大時,兩者執行時間的差異也越大,所以當在不同線程間傳輸的資料型態是 Transferable objects 時,請記得傳遞 transfer 參數增進效能。

Transferable objects 型別

最後來看看有哪些物件是可以被轉移的呢? MDN 中提到支援的有:

我想以上列出的蠻多型別不是一般常用到的,所以接下來幾天我打算先介紹這些型別的用法,然後再來看看他們在什麼場景下會跟 Web worker 搭配使用吧~

Reference

Transferable objects
Transferable objects - Lightning fast


上一篇
postMessage 速度測試
下一篇
Transferable objects - ArrayBuffer
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言