最速のPromiseライブラリ作る方法

## はじめに

[`Aigle`](https://github.com/suguru03/aigle)というPromiseライブラリを開発しています。このライブラリは[ベンチマーク](https://github.com/suguru03/aigle/tree/master/benchmark#benchmark)上だけでなく実際の本番環境を考慮した高速化を実現しています。

今回はその過程で`Bluebird`に大変お世話になったので、なぜ`Bluebird`が速いのか、そしてなぜ`Aigle`が速いのかついて説明したいと思います。

## 基本的なPromiseの知識

まず本題に入る前にPromiseの基本的なことを説明しておきます。
Promiseには`pending`, `fulfilled`, `rejected`の３つの状態があります。一度`pending`から別の状態に変わった場合は、そのPromiseの状態が変わることはありません。

![promise](https://mdn.mozillademos.org/files/8633/promises.png)

(引用: [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise))

またPromiseは非同期であることが保証されています。この事は少し重要なので触れておきます。

```js
new Promise(resolve => resolve(1)) // 同期
  .then(num => {
    // 非同期
  });
```

もっと詳しく知りたい方は[コチラ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)を読んで下さい。

## 高速なライブラリを作る上で重要なこと

本題に入る前に、高速なライブラリを作る上で重要なことについて触れておきます。[こちら](http://postd.cc/javascript-performance-fundamentals-make-bluebird-fast/)にも詳しく書かれていますが、私が思う重要なことは以下の３点です。

- **無駄な変数・関数・インスタンスを生成しない**
- **無駄な関数の実行をしない**
- **非同期処理をスマートに行う**

v8(JS実行エンジン)も読んでいないですし、bit演算レベルの配慮もしていませんし、そんなにギークでは無い私ですが、高速なライブラリは上記３つに従えば作れると思っています。

### 無駄な変数・関数・インスタンスを生成しない

上記の記事にも書かれていますが、基本的に無駄なものを作成しないということは、不要なメモリの割当をしないということになります。以下の例を見てみます。

```js
function sum(array) {
  return array.reduce(function iterator(result, num) {
    return result + num;
  });
}

sum([1, 2, 3]); // 6
```
この`iterator`という関数は`sum`が呼ばれる度に生成されてしまいます。このコードは以下のように修正できます。

```js
function iterator(result, num) {
  return result + num;
}

function sum(array) {
  return array.reduce(iterator);
}

sum([1, 2, 3]); // 6
```

これにより不要な関数の生成を回避できます。

また以下の例を見てみます。

```js
function get(type) {
  const array = [];
  const object = {};
  const num = 0;
  const str = '';
  switch (type) {
  case 'array':
    return array;
  case 'object':
    return object;
  case 'number':
    return num;
  case 'string':
    return str;
  }
}
get('string');
```

この場合、欲しいのは`str`だけになりますので、`array`, `object`, `num`は不要な割当になります。この例ではパフォーマンスは大差ありませんが、これがインスタンスの生成や関数の生成になってくると少しずつパフォーマンスに差が出てきます。そのため以下のように書き換えます。

```js
function get(type) {
  switch (type) {
  case 'array':
    return [];
  case 'object':
    return {};
  case 'number':
    return 0;
  case 'string':
    return '';
  }
}
get('string');
```

必要なときだけ変数・関数・インスタンスの生成を行うことで高速化できます。

### 無駄な関数の実行をしない

例えばapiを作成するときにリクエストのパラメータをチェックします。

```js
function api(req, res) {
  const { id } = req.body;
  if (!isNumber(id)) {
     return res.sendStatus(400);
  }
  innerFunc(id)
    .then(...)
    .catch(...)
}

function isNumber(id) {
  return typeof id === 'number';
}

function innerFunc(id) {
  if (!isNumber(id)) {
    return Promise.reject(new Error('error'));
  }
  ...
}
```

apiを実装する上では、内部関数のところでチェックしたほうが良いケースも多々ありますが、ライブラリを作る上では不要です。基本的に内部関数の呼び出しの前で一度チェックされているので不要な関数呼び出しになります。

### 非同期処理をスマートに行う

これは以下の`Bluebird`の例を見ながら説明していきます。

## なぜ`Bluebird`が速いのか

`Bluebird`を開くとまず目につくのが`bitField`です。これは思考を停止させる会心の一撃と言っても過言ではないでしょう。しかし今は忘れてください。

`Bluebird`には大きくわけて2つの状態があり、`pending`とそれ以外で処理が異なります。その状態を管理するために`Bluebird`では`bitField`が使われています。しかし今は忘れてください、処理が大きく異なるのは`pending`かそれ以外です。

### `pending`の場合

以下の例を見てみましょう。

```js
new Bluebird(function executor(resolve) {
  // この関数は同期的に呼ばれる
  setTimeout(function timer() {
    resolve(1); // このresolveは非同期で呼ばれる
  }, 10);
})
.then(function onFulfilled(value) {
  // 非同期が保証されている
});
```

この実行順序は
1. `Bluebird`インスタンス(親)の生成
2. `executor`の実行
3. `then`の実行
4. `timer`の実行
5. `resolve`の実行
6. `onFulfilled`の実行
です。
`then`の実行時に[`Bluebird`インスタンスが生成され](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L228)、親の[インスタンスの子要素として紐付きます](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L416)。この時、`timer`内の`resolve`は実行されていないので親のインスタンスは`pending`になります。その後`setTimeout`により`timer`が実行され[`resolve`が呼ばれます](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L513)。そして[その結果を子要素に反映していき、](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L703-L710)`onFulfilled`が実行されます。
状態が`pending`のときは親に子要素をセットする。親の結果が出たら子要素を実行する、ただこれだけです。

### `pending`以外の場合

次の例は同期的呼び出しの例です。

```js
new Bluebird(function executor(resolve) {
  resolve(); // 同期的に実行される
})
.then(function onFulfilled(value) {
  // 非同期を保証しなければならない
});
```

実行順序は
1. `Bluebird`インスタンス(親)の生成
2. `executor`の実行
3. `resolve`の実行
4. `then`の実行
5. `onFulfilled`の実行
です。
この場合、`then`が呼ばれる前に`resolve`が呼ばれており、親の`Bluebird`インスタンスはすでに`pending`以外の状態になっています。この時に`onFulfilled`をそのまま呼び出してしまうと、同期的に呼ばれてしまうため非同期が保証されなくなってしまいます。
そこで内部で非同期関数を呼ぶ必要があります。まず内部で非同期を保証する[`async.invoke`という関数](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/promise.js#L263-L270)が呼ばれます。これは非同期を引き起こすという意味です。そして子要素の`onFulfilled`は[`queue`にセットされます](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/async.js#L88)。そして内部で[`schedule`という関数が呼ばれます](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/async.js#L159)。この[`schedule`](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/schedule.js#L3-L77)とは`Node.js`では`setImmediate`です。この`setImmediate`により[子要素が非同期で実行されます](https://github.com/petkaantonov/bluebird/blob/v3.4.7/src/async.js#L144)。

`pending`以外のときは`queue`にセットして非同期関数を呼ぶ。非同期関数により`queue`の要素を非同期で実行する。シンプルであるがゆえに高速です。

ここで疑問に思うのが、なぜわざわざ`queue`にセットする必要があるのか、という点です。ただ非同期処理をしたいのであればそのまま非同期関数をそのまま呼べば良いのです。ここが`Bluebird`の非常にスマートな点です。

### 非同期のスマートな処理

以下の例を見てみましょう。

```js
Bluebird.resolve(1) // 同期的に呼ばれる
  .then(num => console.log(num)); // 非同期でなければならない
Bluebird.resolve(2) // 同期的に呼ばれる
  .then(num => console.log(num)); // 非同期でなければならない
Bluebird.resolve(3) // 同期的に呼ばれる
  .then(num => console.log(num)); // 非同期でなければならない
```

実際にはこのようなコードは書きませんが、複雑な非同期の並列処理をする際にこのような同期的に呼ばれるケースが多々存在します。この際、毎回非同期関数を呼び出してしまうとパフォーマンス低下につながるため、`Bluebird`では全ての同期呼び出しを`queue`に追加し、一度の非同期関数の実行で全ての同期呼び出しを非同期で呼び出しています。

はっきり言って`bitField`がどこまで恩恵を与えているのかがわかりません、もしかしたらすごい恩恵を与えているのかもしれません。しかし`Bluebird`が本当に速い理由はこのシンプルさにあると思います。

## なぜ`Aigle`が速いのか

### ３つの原則

- **無駄な変数・関数・インスタンスを生成しない**
- **無駄な関数の実行をしない**
- **非同期処理をスマートに行う**

基本的に上記の３つの原則をまもる、これ以外にすることはありません。特別なことはしてません、私にはできません。しかしこの３つの原則を守れば速いコードは作れます。ベンチマークの結果が[こちら](https://github.com/suguru03/aigle/tree/master/benchmark#results)です。

|benchmark|aigle|bluebird|
|---|---|---|
|all|**31.3μs**|42.3μs [0.741]|
|join|**1.62μs**|2.59μs [0.626]|
|map|**35.2μs**|57.5μs [0.613]|
|promise:then|**178μs**|348μs [0.511]|
|promisify|**2.06μs**|28.0μs [0.0737]|
|promisifyAll|**28.8μs**|118μs [0.245]|
|props|**56.8μs**|69.1μs [0.821]|
|race|**30.4μs**|42.8μs [0.712]|
|using|**2.67μs**|9.60μs [0.278]|

シンプルがゆえに高速です。ただそれだけです。

### 本番環境を考慮した高速化

`Aigle`の本当にすごいところは[`aigle-core`](https://github.com/suguru03/aigle-core)と呼ばれる`dependency`です。
`Bluebird`では`bitField`を管理するために全てのPromiseインスタンスが`Bluebird`インスタンスである必要があります。またそれをチェックするために`instanceof`を使用します。`instanceof`でチェックしたPromiseが`Bluebird`のインスタンスではない場合、新たに`Bluebird`のインスタンスを生成しラップします。しかしこの`instanceof`ですが、`Bluebird`上では現在のクラスのインスタンスであるかどうかをチェックしています。例えば、`v3.4.6`と`v3.4.7`の`Bluebird`を併用した場合、新たにインスタンスが作られてしまいます。
`Aigle`ではこれを避けるべく、`aigle-core`という別の`dependency`を持っています。全ての`Aigle`クラスは共通の`AigleCore`を継承することにより、この上記のパフォーマンス低下を防いでいます。

実際に作った[ベンチマークツール](https://github.com/suguru03/aigle-benchmark)を見てみます。

```
$ npm list
aigle-benchmark@0.0.0
├─┬ aigle@0.4.1
│ └── aigle-core@0.2.0 <- aigle-core v0.2.0
├─┬ benchmark@2.1.3
│ └── platform@1.3.3
├── bluebird@3.4.7
├── lodash@4.17.4
├── minimist@1.2.0
└─┬ promise-libraries@0.3.0
  ├── aigle@0.4.0 <- aigle-core v0.2.0
  └── bluebird@3.4.6
```

npmでは同じバージョンの`dependency`のときに多重インストールはされずに一つだけインストールされます。この時、`v0.4.1`と`v0.4.0`では共通の`aigle-core`が使用されます。これが`Aigle`です。

ベンチマークの結果はこちらです。ベンチマークには[`benchmark.js`](https://github.com/bestiejs/benchmark.js)を使用しています。コードは少し長いので[こちら](https://github.com/suguru03/aigle-benchmark/blob/2d6ccfd2ee6542f9a9300c58da78e5eb6402ce6f/tasks/promise.js#L11-L52)を参照してください。

環境は以下の通りです。

- Aigle v0.4.0, v0.4.1
- Bluebird v3.4.6, v3.4.7
- Node v6.9.1

```
$ node --expose_gc . -t then
======================================
[Aigle] v0.4.1
[Bluebird] v3.4.7
======================================
[promise:then:same] Preparing...
--------------------------------------
[promise:then:same] Executing...
[1] "aigle" 179μs[1.00][1.00]
[2] "bluebird" 334μs[0.535][1.87]
======================================
[promise:then:diff] Preparing...
--------------------------------------
[promise:then:diff] Executing...
[1] "aigle" 180μs[1.00][1.00]
[2] "bluebird" 512μs[0.351][2.85]
```

`promise:then:same`は全て同じバージョン、`promise:then:diff`では子要素を異なるバージョンを使用しています。異なるバージョンを使用した場合、`Bluebird`ではパフォーマンスが顕著に低下してしまいます。これは私が作ったベンチマークなので、測定方法によりまた違う結果が出るかもしれません。ぜひ計測してみてください。

## まとめ
`Bluebird`は本当に高速で特に`join`が高速です。
`Aigle`は速いだけではなく、便利でかつ安全に使えるライブラリを目指しています。`Aigle`は[`Async`](https://github.com/caolan/async), [`Neo-Async`](https://github.com/suguru03/neo-async)の関数をそのまま保持しているので、まだ`callback`スタイルを使っている方も簡単に移行できると思います。

少しでもこの`Node.js`、`JavaScript`界隈にインパクトを与えられたら嬉しいです。
まだまだ`Aigle`も改善の余地があります、ぜひレビュー・PRいただけたらと思います。

ちなみにえいぐるって読みます、フランス語です。

## リンク
- [`Aigle`](https://github.com/suguru03/aigle)
- [`Bluebird`](https://github.com/petkaantonov/bluebird)
- [MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)