web worker 的傳值方式以及耗時對比

背景

前一陣子開發的項目 pptx 導入, 由於自己的代碼問題,引起了個性能問題,一個 40p 的 pptx 文件,轉換成 json 數據,大概要耗時 60s+ ,雖然後面發現是某個使用頻率非常高的函數內部,用了 new Function 構造函數 造成的(所以這裏順便提醒一下,如果你很在乎幾毫秒的差距的話,建議謹慎使用哈),但是在優化的過程中,一度懷疑是性能達到了瓶頸,所以嘗試了使用 web worker 去優化,由於是文件,一般內容都比較大,發現 web worker 在傳值這塊佔用了大部分的時間,所以想開這篇來詳細聊聊.

兩種傳值方式

關於 web worker 的基本用於以及傳值方式,網上以及有一大堆介紹了,這裏就不贅述了,這裏我們重點來看一下同一個文件用兩種方式來傳值,會有多大的差別,這邊隨意從電腦裏面找了一個 96MB 的 PSD 文件來測試.

主線程

    fetch('./case.psd').then(file => {
            return file.blob();
        })

        .then(blob => {
            return new Promise(resolve => {
                let fileReader = new FileReader();
                fileReader.onload = e => {
                    resolve(e.target.result);
                }
                fileReader.readAsArrayBuffer(blob);
            })
        })

        .then(buf => {
            let worker = new Worker('1.js');

            console.time('計算時間');
            worker.postMessage(buf);

            worker.onmessage = e => {
                console.timeEnd('計算時間');
            }


        })

worker(子)線程, 這裏爲了避免不必要的因素干擾,worker 線程裏面什麼也不做,在收到消息後,直接 post 一個消息回去

    self.onmessage = e => {
        postMessage(0);
    }

這邊我直接用 FileReader 的 readAsArrayBuffer,讀出來是一個長度爲 96,138,230 的字符串,長度大概 0.96 億, 耗時大概 70ms 左右(同一個臺電腦取 10 次平均值,下同)

我們稍微改一下上面主線程的代碼,改用 轉移數據 的方式

- worker.postMessage(buf);

+ worker.postMessage(buf, [buf]);

同樣的數據, 耗時大概 17ms 左右,這 17ms 好像是個固定值,我嘗試換了個 800MB+ 的文件和一個裏面啥都沒有的空文本文件,大概都是這個時間.

不同的數據類型,用值傳遞的耗時也是不一樣的

    fetch('./case.psd').then(file => {
            return file.blob();
        })

        .then(blob => {
            return new Promise(resolve => {
                let fileReader = new FileReader();
                fileReader.onload = e => {
                    resolve(e.target.result);
                }
                fileReader.readAsText(blob);
            })
        })

        .then(str => {
            console.log(str.length);
            let worker = new Worker('1.js');

            console.time('計算時間');
            worker.postMessage(str);

            worker.onmessage = e => {
                console.timeEnd('計算時間');
            }


        })

這裏我們改用 FileReader 的 readAsText,讀出來是一個長度爲 95,855,954 的字符串,長度大概 0.95 億, 耗時大概 118ms 左右,同樣我換了上面那個裏面啥都沒有的空文本文件,耗時也是 17ms 左右.

那我們試試用 readAsDataURL 看看讀出來的數據要多久

    fetch('./case.psd').then(file => {
            return file.blob();
        })

        .then(blob => {
            return new Promise(resolve => {
                let fileReader = new FileReader();
                fileReader.onload = e => {
                    resolve(e.target.result);
                }
                fileReader.readAsDataURL(blob);
            })
        })

        .then(str => {
            console.log(str.length);
            let worker = new Worker('1.js');

            console.time('計算時間');
            worker.postMessage(str);

            worker.onmessage = e => {
                console.timeEnd('計算時間');
            }


        })

讀出來是一個長度爲 128,184,345 的字符串,長度大概 1,28 億, 耗時大概 85ms 左右(雖然字符串長度更長,但是耗時卻更短)

以上耗時,均爲主線成向 worker 線程單向傳遞數據的耗時.

結論

  1. 轉移數據幾乎是零開銷(因爲和傳遞空字符串的耗時是差不多的).
  2. 值傳遞的話,不同的數據類型,耗時也有差別,ArrayBuffer < base64 < 普通字符串.
  3. postMessage 傳遞消息,除了發送數據的耗時外,還有其他開銷(就是上面的 17ms). 當然每臺電腦性能不一樣,耗時也是不一樣的,不過按比例來看,這個佔比還挺大的.

關於轉移的缺點, 網上也是有很多的, 這裏也就不囉嗦了, 總結一句就是數據無法同時在2個線程上使用.

另外個人覺得如果是普通的數據,爲了轉移而去轉換成 Transferable objects 的話, 大部分情況下是划不來的, 因爲你需要在花在編碼解碼上的時間,會比直接傳遞花的時間多.

另外, 如果你是要用子線程處理圖片的話, ImageBitmap 格式 配合最近新鮮出爐的 OffscreenCanvas 也許是不錯的選擇.前提是你不需要考慮兼容性問題.

最後是廣告時間

我們40人的前端團隊常年招兵買馬中,在廈門的和想來廈門的童鞋們,不要吝惜你的簡歷,使勁砸過來 郵箱:[email protected], 期待你一起來稿事

原文地址 https://github.com/noahlam/ar...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章