iT邦幫忙

0

Caco的奇幻之旅2-Web Worker即時渲染效能實測(上)

  • 分享至 

  • xImage
  •  

前言

大家好,這裡是Caco的不嚴謹Web Worker效能測試,繼上次的Loka Veterra後,我為粒子加入了簡單的物理引擎,每個粒子都會被中心點的重力所吸引。

隨著演算法越趨複雜,掉禎的情況越趨明顯,於是,我希望透過在背景進行非同步的運算,來提升純前端的演算能力,每新增一個worker,理論上效率就會加倍,就算不是100%,至少也有80%對吧?就讓我們來實測看看吧。

不負責任聲明:小弟Caco第一次接觸Web Worker,因此不過多著墨技術名詞以免誤人子弟

Go Go Power Workers!

如圖所示,左邊是在主線程中(main.js)運算的粒子系統,右邊則是在獨立線程中(worker.js)運算,下方我做了一個簡易的UI,可以知道當前畫面的禎數(左右各有2000個粒子,運算量約為4000個繪圖請求、4000次座標計算的迴圈)
https://ithelp.ithome.com.tw/upload/images/20240125/20135197O2z0qFl7BW.png

什麼?獨立線程的禎數掉得更快嗎?

如果你心裡有這個想法,別緊張!基本上,我就是把原本的粒子系統、渲染系統的代碼複製了一份,貼到worker.js中,為什麼這麼做呢?因為方便,與其直奔渲染+演算協作模式,不如先用同樣的運算量,測試看看worker的能耐。

worker本身就屬於另外一個執行緒,不可能占用系統太多資源,能力差一點也是情有可原,不過這掉禎的速度確實有驚訝到我。

在worker中使用Canvas API的手把手教學

主要的知識點是worker可以調用Canvas API(真是太棒了!),乍聽之下讓人以為可以把繪圖的工作全部交給worker(不是嗎?),事實上,只有offScreenCanvas能夠給worker操作,無須多言,其意譯就是螢幕外的畫布,於是在worker.js中,讓它根據要求,創建一個新的畫布:

圖一
https://ithelp.ithome.com.tw/upload/images/20240125/20135197FRRb7lhZLW.png

worker可以接收的訊息類型蠻多的,但沒時間跟你解釋了,先上車吧!只要不是奇怪的東西,基本都可以被postMessage方法複製一份。所以我選擇在main.js裡面寫一個物件,包含給它的指令、畫布的寬和高。

圖二
https://ithelp.ithome.com.tw/upload/images/20240125/20135197iLnal7IRVc.png

接收到bitmap後,用context.transferToImageBitmap繪圖,而且這個context還必須是用getContext("bitmaprenderer")所建立的

接著還要接收worker完成的圖像,必須以bitmap的格式傳送。因此回到work.js中,這邊我也是寫一個物件,把指令和bitmap放進去。

圖三
https://ithelp.ithome.com.tw/upload/images/20240125/20135197RXLRCrEc1r.png

worker真的不會影響到主線程效能嗎?

這是MDN對Web Worker的解釋:

Web Workers 可以在獨立於 Web 應用程式主執行線程的後台線程中運行腳本操作。這樣做的好處是,可以在單獨的線程中執行費力的處理,從而允許主線程(通常是UI)運行而不會被阻塞/減慢。

但是根據我這些天來爬文的資料(其實也爬不太到Orz,2020後的文章少之又少),有一派認為,由於worker不能操作DOM元件,使得通信需求和成本增加,其效率不如優化原本的代碼(不過這只是酸葡萄,他根本沒實操只是說說);還有一派用實驗告訴我們,worker大約兩三個就封頂了,再多也不會更快(好消息是那是好幾年前的事了)

換句話說,worker應該是有副作用的!

首先,我們把粒子的數量從2000增加到5000,可以看到主線程fps顯著地下降了,並且左右的效能比大約從原本的7:5,變成了11:9,不難猜測,兩者之間肯定有相互影響。
https://ithelp.ithome.com.tw/upload/images/20240125/20135197yaKUMPri7j.png
接著,考慮到或許是由於一百萬像素的通信成本導致,我們先把圖三註解掉,讓worker自己畫完圖就完事了,無須擔心顯示問題,此時可以看到fps小幅提升了,看來果然有占用一些資源,但是並不多。
https://ithelp.ithome.com.tw/upload/images/20240125/20135197fDPLAkZRZp.png
最後,我們直接放生worker,把圖二的postMessage註解掉,不給它指令了,換句話說,不讓它計算,此時fps從24來到了28,如此可見,多了一個worker在背景搞事情,對於主線程還是有一定的影響。
https://ithelp.ithome.com.tw/upload/images/20240125/201351974EP9b2wEmU.png

有沒有可能反過來,其實是worker的表現被影響?

基本上不會,但嚴謹的我們還是做一下實驗,把main.js的粒子系統整個註解掉...摁,跟一開始的表現並無二致,一樣是18.19之間。
https://ithelp.ithome.com.tw/upload/images/20240125/20135197hcNWccWHUy.png

問題的關鍵是,我們能用幾個worker?

問題的關鍵是要找到關鍵的問題,那你要找到關鍵的問題,得要先找到問題的關鍵(?

拍謝XD,應該說這問題太難了,小弟不才,對硬體方面、線程、執行緒等知識不足,實在是難以下手,我只能簡化這個議題,不如想想,我每多指派一個worker,對主線程的影響程度有多大?

依序實驗:兩個、五個、八個

每個worker的任務相同,對5000個粒子進行10000次迴圈計算,並處理10000個繪圖請求。
https://ithelp.ithome.com.tw/upload/images/20240125/20135197jBXPmvWhnY.pnghttps://ithelp.ithome.com.tw/upload/images/20240125/201351972ExsIM3jT2.pnghttps://ithelp.ithome.com.tw/upload/images/20240125/20135197lg8CsSqrOz.png

不負責任的效能比較

做一個簡易的計算,將算力當作fps*1來看,再用worker的數量乘上右側的fps:

Worker fps(左) fps(右) equation = cp
2 19 17 19 + 2 x 17 = 53
5 17 14 17 + 5 x 14 = 87
8 14 9 14 + 8 x 9 = 86

如此可見,我的電腦使用到5個worker,就已足以應付目前的需求,且可把禎數提升到至少87禎(介於17和14乘以6之間);另一方面,如果只用2個worker,不一定足夠,但至少有53禎。

結論

透過以上,我們可以知道worker的能力是有限的,假如想要全部交給worker,光是2000個粒子就使禎數下降至43,如此可見至少還需要再一個worker幫忙,才能提升效能,取代主線程的工作。沒錯,目標應該是讓主線程越輕鬆越好,這樣才有更多心力去串接其他系統。

不過,現階段用worker解決效能問題,只不過是治標不治本,當繪圖愈加複雜,愈會力不從心,此時引入函式庫就會是好選擇,例如three.js,其底層使用的WebGL才是能解決效能的秘方!然而這也不代表現在的努力會白費,期待未來有能力使用Worker操作各種不同的函式庫,也是很不錯的。

下集預告(預計2月中)

在寫這篇文章前,我有草擬一個方式,是在跑4000次迴圈計算的時候,逐次傳遞訊息給worker,每次最小化訊息傳輸,並且非同步等待worker回傳粒子的座標,這個方案的優點同時也是缺點,如果算力不足,會有部分的粒子來不及在畫面渲染前更新,然而,因為大部分粒子都有刷新,人眼便看不出來掉禎的問題,即使動畫的速度的確稍有變慢。

也因為,並不能保證worker有能力處理4000次的請求,避免請求不斷堆積,故才要有等待的動作,在歷遍迴圈時,若worker還未回傳上次的請求,就不會發送,比如正常情況下,禎數只有30時(一半),在此設計下,worker僅在60禎畫面刷新前處理並回應了2000次請求,那麼另外2000次就會繼續等待,從而達到滾動式的修正。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言