Napa.js 簡介

原文地址:https://github.com/Microsoft/...node

本文介紹 Napa.js 的核心概念,帶領你們探索 Napa.js 是如何運轉起來的。關於它的由來和開發初衷,能夠閱讀 這篇文章git

簡介

Zone

Zone 是 Napa.js 中的核心概念,它是執行 JavaScript 代碼的基本單元,全部涉及多線程相關的內容都離不開 Zone 這個概念。一個進程能夠包含多個 zone,而每一個 zone 又由多個 JavaScript Worker 組成。github

在 zone 內部的全部 worker 都是類似的:他們加載相同的代碼,幾乎以相同的方式處理 broadcastexecute 請求,你沒法指定執行某一個特定 worker 中的代碼。在不一樣 zone 之間的 worker 是徹底不一樣的:他們加載不一樣的代碼,或者雖然加載相同代碼但以不一樣的策略執行,例如堆棧大小不一樣、安全策略不一樣等。應用會利用多個 zone 來加載不一樣的策略。npm

有兩種類型的 zone:數組

  • Napa zone - 由多個 Napa.js 管理的 JavaScript worker 組成。Napa zone 內的 worker 支持部分 Node.js API
  • Node zone - 暴露了 Node.js event loop 的虛擬 zone,具備完備的 Node.js 能力

這樣劃分讓你既能夠用 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 上能夠作兩種類型的操做:多線程

  1. Broadcast - 全部 worker 執行一樣的代碼,改變 worker 狀態,返回 promise 對象。不過咱們只能經過 promise 的返回結果判斷執行成功仍是失敗。一般用 broadcast 來啓動應用、預加載一些數據或者修改應用設置。
  2. Execute - 在一個隨機 worker 上執行,不改變 worker 狀態,返回一個包含結果數據的 promise。 execute 一般是用來作實際業務的。

Zone 的操做採用「先進先出」的策略,但 broadcastexecute 優先級更高。dom

如下代碼演示了使用 broadcastexecute 完成一個簡單的任務:

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 類型。包括

  • JavaScript 基礎類型:null, boolean, number, string
  • 實現了 Transportable 接口的對象(TypeScript class)
  • 由以上類型構成的數組或對象
  • 還有 undefined

跨 worker 存儲

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 版本已經修復上述問題。

步驟以下:

  1. 安裝先決依賴

    • Install C++ compilers that support C++14:

      • xcode-select --install
    • Install CMake:

      • brew install cmake
    • Install cmake-js:

      • npm install -g cmake-js
  2. 經過 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
相關文章
相關標籤/搜索