最近做一個專案,裡面涉及到在前端做大量計算,直接用js跑了一下,大概需要15s的時間, 也就是使用者的瀏覽器會卡死15s,這個完全接受不了。
雖說有V8這樣牛逼的引擎,但大家知道js並不適合做CPU密集型的計算,一是因為單執行緒,二是因為動態語言。我們就從這兩個突破口入手,首先搞定“單執行緒”的限制,嘗試用WebWorkers來加速計算。
什麼是WebWorkers
簡單說,WebWorkers是一個HTML5的新API,web開發者可以通過此API在後臺執行一個指令碼而不阻塞UI,可以用來做需要大量計算的事情,充分利用CPU多核。
大家可以看看這篇文章介紹https://www.html5rocks.com/en/tutorials/workers/basics/, 或者對應的中文版。
The Web Workers specification defines an API for spawning background scripts in your web application. Web Workers allow you to do things like fire up long-running scripts to handle computationally intensive tasks, but without blocking the UI or other scripts to handle user interactions.
可以開啟這個連結自己體驗一下WebWorkers的加速效果。
現在瀏覽器基本都支援WebWorkers了。
Parallel.js
直接使用WebWorkers介面還是太繁瑣,好在有人已經對此作了封裝:Parallel.js。
注意Parallel.js可以通過node安裝:
1 |
$ npm install paralleljs |
不過這個是在node.js下用的,用的node的cluster模組。如果要在瀏覽器裡使用的話, 需要直接應用js:
1 |
<script src="parallel.js"></script> |
然後可以得到一個全域性變數,Parallel
。Parallel
提供了map
和reduce
兩個函數語言程式設計的介面,可以非常方便的進行併發操作。
我們先來定義一下我們的問題,由於業務比較複雜,我這裡把問題簡化成求1-1,0000,0000的和,然後在依次減去1-1,0000,0000,答案顯而易見: 0! 這樣做是因為數字太大的話會有資料精度的問題,兩種方法的結果會有一些差異,會讓人覺得並行的方法不可靠。此問題在我的mac pro chrome61下直接簡單地跑js執行的話大概是1.5s(我們實際業務問題需要15s,這裡為了避免使用者測試的時候把瀏覽器搞死,我們簡化了問題)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const N = 100000000;// 總次數1億 function sum(start, end) { let total = 0; for (let i = start; i<=end; i += 1) total += i; for (let i = start; i<=end; i += 1) total -= i; return total; } function paraSum(N) { const N1 = N / 10;//我們分成10分,沒分分別交給一個web worker,parallel.js會根據電腦的CPU核數建立適量的workers let p = new Parallel([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) .require(sum); return p.map(n => sum((n - 1) * 10000000 + 1, n * 10000000))// 在parallel.js裡面沒法直接應用外部變數N1 .reduce(data => { const acc = data[0]; const e = data[1]; return acc + e; }); } |
程式碼比較簡單,我這裡說幾個剛用的時候遇到的坑。
- require所有需要的函式
比如在上訴程式碼中用到了sum
,你需要提前require(sum)
,如果sum中由用到了另一個函式f
,你還需要require(f)
,同樣如果f
中用到了g
,則還需要require(g)
,直到你require了所有用到的定義的函式。。。。
- 沒法
require
變數
我們上訴程式碼我本來定義了N1
,但是沒法用
ES6
編譯成ES5
之後的問題以及Chrome沒報錯
實際專案中一開始我們用到了ES6
的特性:陣列解構。本來這是很簡單的特性,現在大部分瀏覽器都已經支援了,不過我當時配置的babel會編譯成ES5
,所以會生成程式碼_slicedToArray
,大家可以線上上Babel測試,然後Chrome下面始終不work,也沒有任何報錯資訊,查了很久,後來在Firefox下開啟,有報錯資訊:
1 |
ReferenceError: _slicedToArray is not defined |
看來Chrome也不是萬能的啊。。。
大家可以在此Demo頁面測試, 提速大概在4倍左右,當然還是得看自己電腦CPU的核數。 另外我後來在同樣的電腦上Firefox55.0.3(64位)測試,上訴程式碼居然只要190ms!!!在Safari9.1.1下也是190ms左右。。。
Refers
- https://developer.mozilla.org/zh-CN/docs/Web/API/WebWorkersAPI/Usingwebworkers
- https://www.html5rocks.com/en/tutorials/workers/basics/
- https://parallel.js.org/
- https://johnresig.com/blog/web-workers/
- http://javascript.ruanyifeng.com/htmlapi/webworker.html
- http://blog.teamtreehouse.com/using-web-workers-to-speed-up-your-javascript-applications