之前寫的某個東西,由於要處理大量數據的最大最小值比較,由於吃的時間有點凶,在想說能不能做到多核心處理。翻閱了很多文件,才知道即使用了像是forEach/Map這種東西,JavaScript還是單執行序處理。難過。
繼續研究之後,才發現有兩個東西,Promise和Worker。Promise看起來應該是還是用同一執行序執行,要善用到多核心,看來需要用到Worker,worker才真的是用到不同的執行序。實測用worker,真的可以把intel 9th H SKU CPU 16執行序塞滿。
但又發現一個問題,用worker本身其實很花時間,照網路上一些解釋,建立worker本身就要花時間,所以單執行序執行時間不夠長的,用worker不一定划算。所以我做了這個
dataset = 1; dataqty = 86400*1; console.time("for loop test");for(j=0;j<dataset;j++)for(i=0,t=0;i<dataqty;i++)t=(t+Math.random())/2;console.timeEnd("for loop test"); console.time("worker test"); response = "onmessage=function(e){for(i=0,t=0;i<"+dataqty+";i++)t=(t+Math.random())/2;postMessage(e.data);}"; wkResult = (new Array(dataset)).fill(false); wkArr = (new Array(dataset).fill(1)).map((e, i) => { // if not fill, map will not action var wk = new Worker( 'data:application/javascript,' + encodeURIComponent(response) ); wk.onmessage = (ee) => { wkResult[ee.data]=true; wkArr[ee.data].terminate(); // if not terminate, browser will out of memory if(!wkResult.includes(false)){ console.timeEnd("worker test"); } }; wk.postMessage(i); return wk; }); //var dateStart = new Date() //while((new Date())-dateStart < 1000 & wkResult.includes(false)){} // cannot set while loop, worker will be set after main thread idle. //console.log(wkResult.includes(false)) //console.timeLog("worker test");
我寫了一個亂數求平均,由於我目標要套用的資料,原始長度有可能會到一天,先假設dataqty=86400,資料先抓只有一筆,dataset就1。前面先用普通方式執行,一個for迴圈掃dataset,一個for迴圈掃時間軸。
後面改用worker去跑。原始資料必須在同數列內計算比較,所以只能用不同數列去拆worker。可以看到,為了建worker,程式碼多了超多出來。首先要先用data:建立一個js,然後worker去開成新worker物件,設定listener,設定條件,最後postMessage等著接訊息。超麻煩。
本來還想說用傳統的寫法,既然都丟worker了,應該可以先發下去然後用主程序設while等回復吧。結果不行,while下去程式死給你看,設while一秒就等一秒,設while十秒就等十秒。最後只能把計時結束放在onmessage內。
調整dataset和dataqty,可以測試不同的數列與不同資料數,到底差多少時間,但這樣太不自動了,所以我又改寫了一下
dataset = 1; dataqty = 86400*1; time_main = []; for(s=0;s<=4;s++) for(q=1;q<6;q++) for(times=0;times<3;times++){ var n=window.performance.now(); dataset=[4,8,16,32,64][s]; dataqty=86400*q; for(j=0;j<dataset;j++)for(i=0,t=0;i<dataqty;i++)t=(t+Math.random())/2; var d=[dataset, q, times, window.performance.now()-n]; time_main.push(d); } console.log(time_main.map(e=>e.join("\t")).join("\n")) time_ws = []; s=0; q=1; times=0; function tester(s,q){ var n=window.performance.now(); dataset=[4,8,16,32,64][s]; dataqty=86400*q; response = "onmessage=function(e){for(i=0,t=0;i<"+dataqty+";i++)t=(t+Math.random())/2;postMessage(e.data);}"; wkResult = (new Array(dataset)).fill(false); wkArr = (new Array(dataset).fill(1)).map((e, i) => { // if not fill, map will not action var wk = new Worker( 'data:application/javascript,' + encodeURIComponent(response) ); wk.onmessage = (ee) => { wkResult[ee.data]=true; wkArr[ee.data].terminate(); // if not terminate, browser will out of memory if(!wkResult.includes(false)){ var d=[dataset, q, times, window.performance.now()-n]; time_ws.push(d); times++; if(!(times<3))times=0,q++; if(!(q<6))q=1,s++; if(s<=4){ tester(s,q); //console.log([s,q]); }else{ console.log(time_ws.map(e=>e.join("\t")).join("\n")) } } }; wk.postMessage(i); return wk; }); } tester(s,q);
我的想法很簡單,掃描不同的dataset,不同的dataqty,然後測三次求平均。
對一般的方式,這很簡單,多加三個loop就結束了。
但進到用worker...喔...程式突然變得超複雜。由於要避免程式不必要的平行跑,必須在真的執行完所有worker時,才執行下一次。所以原本用來輸出的位置被塞滿滿的判斷式,同時也要建成一個function才比較好執行
比較結果如下,以i7-10880H,8c16t執行,單位為毫秒。
dataset | 4 | 8 | 16 | 32 | 64 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dataqty | main | worker | main < worker | main | worker | main < worker | main | worker | main < worker | main | worker | main < worker | main | worker | main < worker | |
86400*1 | 1 | 14.2 | 21.7 | False | 24.2 | 22.3 | False | 30.6 | 41.3 | False | 61.4 | 80.4 | False | 122.7 | 161.2 | False |
2 | 11.7 | 14.3 | 15.3 | 22 | 30.5 | 75.1 | 60.8 | 84.8 | 122.1 | 165.2 | ||||||
3 | 11.1 | 13.8 | 15.5 | 23.4 | 30.5 | 60.1 | 60.8 | 90.3 | 122.4 | 208.3 | ||||||
mean | 12.3 | 16.6 | 18.3 | 22.6 | 30.5 | 58.8 | 61.0 | 85.2 | 122.4 | 178.2 | ||||||
86400*2 | 1 | 17.9 | 14.9 | False | 31.1 | 25.7 | True | 61.2 | 61.9 | True | 122.2 | 92.4 | True | 243.9 | 198.0 | True |
2 | 11.5 | 17.8 | 31.3 | 27 | 61.3 | 45.5 | 122.8 | 93.6 | 244.7 | 186.2 | ||||||
3 | 11.7 | 15 | 30.8 | 25.3 | 61.7 | 53.9 | 122.6 | 93.3 | 244.6 | 181.4 | ||||||
mean | 13.7 | 15.9 | 31.1 | 26.0 | 61.4 | 54.8 | 122.6 | 93.1 | 244.4 | 188.5 | ||||||
86400*3 | 1 | 17.7 | 18.5 | False | 45.7 | 31.4 | True | 91.8 | 54.0 | True | 183.4 | 103.4 | True | 367.2 | 210.4 | True |
2 | 17.8 | 17.2 | 46.0 | 29.0 | 92.3 | 55.7 | 183.9 | 102.7 | 369.4 | 228.4 | ||||||
3 | 17.7 | 20.5 | 46.2 | 30.9 | 91.7 | 53.0 | 181.7 | 106.3 | 365.7 | 200.5 | ||||||
mean | 17.7 | 18.7 | 46.0 | 30.4 | 91.9 | 54.2 | 183.0 | 104.1 | 367.4 | 213.1 | ||||||
86400*4 | 1 | 23.6 | 19.6 | True | 62.3 | 31.8 | True | 122.9 | 61.5 | True | 242.9 | 119.4 | True | 488.6 | 224.3 | True |
2 | 24.4 | 18.6 | 62.1 | 35.3 | 122.6 | 54.4 | 242.9 | 112.8 | 437.7 | 232.9 | ||||||
3 | 26.7 | 24.8 | 62 | 31 | 122.6 | 65.1 | 243.9 | 113.9 | 459.9 | 223.0 | ||||||
mean | 24.9 | 21.0 | 62.1 | 32.7 | 122.7 | 60.3 | 243.2 | 115.4 | 462.1 | 226.7 | ||||||
86400*5 | 1 | 30.6 | 21.3 | True | 78.3 | 35.7 | True | 152.8 | 65.2 | True | 306.2 | 125.3 | True | 564.5 | 253.8 | True |
2 | 32 | 22.5 | 77.3 | 34.1 | 152.7 | 65.1 | 307.4 | 117.5 | 560.3 | 290.7 | ||||||
3 | 30.3 | 21.8 | 76.3 | 36 | 151.6 | 62.0 | 304.5 | 124.0 | 557.4 | 292.0 | ||||||
mean | 31.0 | 21.9 | 77.3 | 35.3 | 152.4 | 64.1 | 306.0 | 122.3 | 560.7 | 278.8 |
可以看到,worker開多不一定能加速,取決於worker內的內容有多長。處理的資料不夠大,開worker可能還會損失因為建立worker所等待的時間,不過這麼大量的數據計算,是否要放在前端由使用者web browser執行就是很值得討論的了。blogger沒有簡易的table編輯器,這邊都我自己key HTML code手key數字的,排版就隨便了。
結論,使用worker前要先評估怎麼建立才能真正加速,必要在使用者端做很長的運算再來用worker。
沒有留言:
張貼留言