原文地址:https://github.com/Microsoft/...node
本文介紹 Napa.js 的核心概念,帶領你們探索 Napa.js 是如何運轉起來的。關於它的由來和開發初衷,能夠閱讀 這篇文章git
Zone 是 Napa.js 中的核心概念,它是執行 JavaScript 代碼的基本單元,全部涉及多線程相關的內容都離不開 Zone 這個概念。一個進程能夠包含多個 zone,而每一個 zone 又由多個 JavaScript Worker 組成。github
在 zone 內部的全部 worker 都是類似的:他們加載相同的代碼,幾乎以相同的方式處理 broadcast
和 execute
請求,你沒法指定執行某一個特定 worker 中的代碼。在不一樣 zone 之間的 worker 是徹底不一樣的:他們加載不一樣的代碼,或者雖然加載相同代碼但以不一樣的策略執行,例如堆棧大小不一樣、安全策略不一樣等。應用會利用多個 zone 來加載不一樣的策略。npm
有兩種類型的 zone:數組
這樣劃分讓你既能夠用 Napa zone 處理繁重的計算事務,也能夠用 Node zone 處理 IO 事務。同時 Node zone 也是對 Napa zone 沒法完整支持 Node API 的一種補充。xcode
如下代碼建立了一個包含 8 個 worker 的 Napa zone:promise
var napa = require('napajs'); var zone = napa.zone.create('sample-zone', { workers: 8 });
如下代碼演示如何訪問 Node zone:安全
var zone = napa.zone.node;
在 zone 上能夠作兩種類型的操做:多線程
broadcast
來啓動應用、預加載一些數據或者修改應用設置。execute
一般是用來作實際業務的。Zone 的操做採用「先進先出」的策略,但 broadcast
比 execute
優先級更高。dom
如下代碼演示了使用 broadcast
和 execute
完成一個簡單的任務:
function foo() { console.log('hi'); } // This setups function definition of foo in all workers in the zone. zone.broadcast(foo.toString()); // This execute function foo on an arbitrary worker. zone.execute(() => { global.foo() });
因爲 V8 不適合在多個 isolate 間執行 JavaScript 代碼,每一個 isolate 管理本身內部的堆棧。在 isolate 之間傳遞值須要封送/拆收(marshalled/unmarshalled),載荷的大小和對象複雜度決定着通訊效率。全部 JavaScript isolate 都屬於同一個進程,且原生對象能夠被包裝成 JavaScript 對象,咱們嘗試在此基礎上爲 Napa 設計一種高效傳輸數據的模式。
爲了實現上述模式,引入瞭如下概念:
可傳輸類型是指能夠在 worker 中自由傳輸的 JavaScript 類型。包括
Transportable
接口的對象(TypeScript class)Store API 用於在 JavaScript worker 中共享數據。當執行 store.set
時,數據被封送到 JSON 並存儲在進程的堆棧中,全部線程均可以訪問;當執行 store.get
時,數據被拆收出來。
如下代碼演示如何利用 store 共享數據:
var napa = require('napajs'); var zone = napa.zone.create('zone1'); var store = napa.store.create('store1'); // Set 'key1' in node. store.set('key1', { a: 1, b: "2", c: napa.memory.crtAllocator // transportable complex type. }; // Get 'key1' in another thread. zone.execute(() => { var store = global.napa.store.get('store1'); console.log(store.get('key1')); });
儘管很方便,但不建議在同一個事務裏用 store 傳值,由於這樣作不單單隻傳輸了數據(還附帶了別的事情,好比加鎖)。另外,雖然有垃圾回收機制,但開發者仍是應當在使用完數據後手動刪除相應的 key。
執行 npm install napajs
安裝。
在 OSX 系統安裝後執行會報錯,在 github issue 中裏也有一樣的提問,解決方法是按照官方的構建文檔,本身手動構建。 最新版 v0.1.4 版本已經修復上述問題。
步驟以下:
安裝先決依賴
Install C++ compilers that support C++14:
xcode-select --install
Install CMake:
brew install cmake
Install cmake-js:
npm install -g cmake-js
經過 npm 構建
npm install --no-fetch
下面是一個計算 π 值的例子,演示瞭如何利用多線程執行子任務。
var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); // Estimate the value of π by using a Monte Carlo method function estimatePI(points) { var i = points; var inside = 0; while (i-- > 0) { var x = Math.random(); var y = Math.random(); if ((x * x) + (y * y) <= 1) { inside++; } } return inside / points * 4; } function run(points, batches) { var start = Date.now(); var promises = []; for (var i = 0; i < batches; i++) { promises[i] = zone.execute(estimatePI, [points / batches]); } return Promise.all(promises).then(values => { var aggregate = 0; values.forEach(result => aggregate += result.value); printResult(points, batches, aggregate / batches, Date.now() - start); }); } function printResult(points, batches, pi, ms) { console.log('\t' + points + '\t\t' + batches + '\t\t' + NUMBER_OF_WORKERS + '\t\t' + ms + '\t\t' + pi.toPrecision(7) + '\t' + Math.abs(pi - Math.PI).toPrecision(7)); } console.log(); console.log('\t# of points\t# of batches\t# of workers\tlatency in MS\testimated π\tdeviation'); console.log('\t---------------------------------------------------------------------------------------'); // Run with different # of points and batches in sequence. run(4000000, 1) .then(result => run(4000000, 2)) .then(result => run(4000000, 4)) .then(result => run(4000000, 8))
運行結果以下,當設置爲 1 組、2 組、4 組子任務並行計算時,能夠看出執行時間有明顯提高,當設置爲 8 組子任務並行計算時,因爲沒有更多的空閒 worker 資源,也就沒有明顯的執行時間的提高。
# of points # of batches # of workers latency in MS estimated π deviation --------------------------------------------------------------------------------------- 40000000 1 4 1015 3.141619 0.00002664641 40000000 2 4 532 3.141348 0.0002450536 40000000 4 4 331 3.141185 0.0004080536 40000000 8 4 326 3.141620 0.00002724641
var napa = require("napajs"); // Change this value to control number of napa workers initialized. const NUMBER_OF_WORKERS = 4; // Create a napa zone with number_of_workers napa workers. var zone = napa.zone.create('zone', { workers: NUMBER_OF_WORKERS }); /* Fibonacci sequence n: | 0 1 2 3 4 5 6 7 8 9 10 11 ... ------------------------------------------------------------------------- NTH Fibonacci: | 0 1 1 2 3 5 8 13 21 34 55 89 ... */ function fibonacci(n) { if (n <= 1) { return n; } var p1 = zone.execute("", "fibonacci", [n - 1]); var p2 = zone.execute("", "fibonacci", [n - 2]); // Returning promise to avoid blocking each worker. return Promise.all([p1, p2]).then(([result1, result2]) => { return result1.value + result2.value; }); } function run(n) { var start = Date.now(); return zone.execute('', "fibonacci", [n]) .then(result => { printResult(n, result.value, Date.now() - start); return result.value; }); } function printResult(nth, fibonacci, ms) { console.log('\t' + nth + '\t' + fibonacci + '\t\t' + NUMBER_OF_WORKERS + '\t\t' + ms); } console.log(); console.log('\tNth\tFibonacci\t# of workers\tlatency in MS'); console.log('\t-----------------------------------------------------------'); // Broadcast declaration of 'napa' and 'zone' to napa workers. zone.broadcast(' \ var napa = require("napajs"); \ var zone = napa.zone.get("zone"); \ '); // Broadcast function declaration of 'fibonacci' to napa workers. zone.broadcast(fibonacci.toString()); // Run fibonacci evaluation in sequence. run(10) .then(result => { run(11) .then(result => { run(12) .then(result => { run(13) .then(result => { run(14) .then(result => { run(15) .then(result => { run(16) }) }) }) }) }) })
運算結果
Nth Fibonacci # of workers latency in MS ----------------------------------------------------------- 10 55 4 10 11 89 4 13 12 144 4 15 13 233 4 22 14 377 4 31 15 610 4 50 16 987 4 81