概述
web worker是瀏覽器的多執行緒機制,多用於處理不涉及DOM操作的密集計算任務,例如演算法計算,資料請求處理等,我正在開發的開源地圖引擎 maptalks.js 中對地圖資料的處理就很適合放到web worker中(突兀的硬廣 ^_^)。
web worker最大的優點是形式簡單:
- worker的執行上下文(context)與主執行緒獨立,無法互相呼叫,避免了多執行緒程式設計中的執行緒鎖等複雜機制
- worker只能通過postMessage和onmessage與主執行緒交換資料
所以,worker程式的效能很大程度上取決於與主執行緒之間資料交換效率。
- 預設情況下,postMessage中資料是採用結構化克隆(structrured clone)演算法來實現跨程式傳送的。
- 引進Web Worker介面時,瀏覽器同時引進了Transferable和ArrayBuffer這一對好基友,當傳送的資料為Transferable時,瀏覽器能通過只傳送資料引用(指標),而不是結構化克隆(structrured clone)來極大的提高資料傳送效率,具體可以參考google的 Transferable Objects: Lightning Fast!(需翻牆)。
問題
當傳送資料結構不復雜時(型別化陣列,字串等),我們有兩種可選的資料傳送方式:
- 直接用postMessage({ data })傳送,讓瀏覽器用structured clone演算法拷貝資料到主執行緒
- 參考谷歌文章,將資料先轉化為ArrayBuffer(Transferable),然後直接傳引用給主執行緒,無需structured clone
如果資料本身已經是型別化陣列,毫無疑問方式二更好。
但如果有很多簡單資料(型別化陣列或字串),將這些資料轉化為ArrayBuffer後採用Transferable傳送,相比瀏覽器的structured clone,哪個效率更高?
測試
為此,我寫了一個jsperf測試程式。
測試資料為100K位元組的整數陣列和200K位元組的字串陣列,建立以下四個測試用例:
- 封裝成物件,直接傳送資料 (structured clone傳送)
- 拷貝為新的陣列後,封裝成物件傳送 (structured clone傳送)
- 將陣列轉化為ArrayBuffer,轉化方式用傳統的遍歷迴圈資料 (Transferable傳送)
- 將陣列轉化為ArrayBuffer,轉化方式用TypedArray.prototype.set批量轉化 (Transferable傳送)
測試結果取決於瀏覽器:
- 在chrome 64和firefox 58下, 都是方式4最快,但chrome上方式3最慢
- firefox下方式1,2最慢,與3,4差距很大
- 在ie 11下,1最快,3最慢,但4與1的差距並不大
結論
- 綜合來看,資料結構簡單時,轉化成ArrayBuffer,用Transferable方式傳送效能更好
- ArrayBuffer的轉化方式很重要,用Array.prototype.set,而不是迴圈遍歷
- 除了100K + 200K,也測試了其他資料,結論仍然成立
最後附上我本機的測試結果:
Chrome 64:
![web worker和主執行緒的資料交換效率初探](https://i.iter01.com/images/24fedc476804b7c1dbe869beb9d7d789a5abef1dc24034391da649e3afbb97ae.png)
Firefox 58:
![web worker和主執行緒的資料交換效率初探](https://i.iter01.com/images/177beb4c0aebb4f5f3705a6d492cab30ca2cea15a8c7b613ad961466ce288843.png)
IE11:
![web worker和主執行緒的資料交換效率初探](https://i.iter01.com/images/3c3b4126c5bae9fc34a00a1630786ab72b5dfe9b1531774b58db467232319cc8.png)