SIMD.js (ES.next stage3)


## はじめに

 * [JavaScript stage 0,1,2,3 Advent Calendar 2015](http://qiita.com/advent-calendar/2015/esnext) の記事です
 * [SIMD.js](https://github.com/tc39/ecmascript_simd)を紹介します
 * 記事執筆時で stage3 の仕様です

## これはなに

JavaScriptで複数のデータを一度に扱ってより高速に処理を行うための新機能（データ型とAPI）です。

１回の命令で複数のデータを並列に処理できる[SIMD](https://ja.wikipedia.org/wiki/SIMD)という手法があります。大抵のCPUは拡張命令としてSIMDに対応しています（MMX, SSE, NEON, etc...）。 **SIMD.js** はJavaScriptからそのSIMDの拡張命令を使おうというものです。

## 新しいデータ型「SIMD型」

具体的には、新しいプリミティブなデータ型として「SIMD型」というものが定義されます。

```js:SIMD型の例

var x = SIMD.Float32x4(0.1, 1.2, 3.2, 3.2);
console.log(typeof x);	//"float32x4"

```

新しく追加されるSIMD型は次の通りです。

```js:ES.nextで追加される予定のSIMD型
SIMD.Int8x16
SIMD.Int16x8
SIMD.Int32x4
SIMD.Uint8x16
SIMD.Uint16x8
SIMD.Uint32x4
SIMD.Float32x4
SIMD.Bool8x16
SIMD.Bool16x8
SIMD.Bool32x4
```

SIMD型はこれまでのJavaScriptで例えるなら、「要素数が決まっている`TypedArray`のようなもの」です。

例えば、`SIMD.Int8x16`は **8** bit のsigned integerが **16個** 並んだものです。名前の最後の数字が"要素"数（SIMD型では「レーン」と呼びます」）になります。

SIMD型は各数値のビット数とレーン数を掛けた合計が必ず「128」になります。SIMD.jsはこの128-bit単位でまとめる（制限する）ことで、CPUのSIMD拡張命令を生かして並列に処理を行います。この同時に扱うデータサイズが固定であるところがただの配列[^1]である`TypedArray`と異なるところです。

<table>
  <tr>
    <th>データ型</th>
    <th colspan="16">128-bit SIMDレジスタ 内の各レーン</th>
  </tr>
  <tr>
    <th>Int8x16<br>Uint8x16</th>
    <td>s0</td>
    <td>s1</td>
    <td>s2</td>
    <td>s3</td>
    <td>s4</td>
    <td>s5</td>
    <td>s6</td>
    <td>s7</td>
    <td>s8</td>
    <td>s9</td>
    <td>s10</td>
    <td>s11</td>
    <td>s12</td>
    <td>s13</td>
    <td>s14</td>
    <td>s15</td>
  </tr>
  <tr>
    <th>Int16x8<br>Uint16x8</th>
    <td colspan="2">s0</td>
    <td colspan="2">s1</td>
    <td colspan="2">s2</td>
    <td colspan="2">s3</td>
    <td colspan="2">s4</td>
    <td colspan="2">s5</td>
    <td colspan="2">s6</td>
    <td colspan="2">s7</td>
  </tr>
  <tr>
    <th>Int32x4<br>Uint32x4<br>Float32x4</th>
    <td colspan="4">s0</td>
    <td colspan="4">s1</td>
    <td colspan="4">s2</td>
    <td colspan="4">s4</td>
  </tr>
</table>

なお、SIMDブーリアン型(例：`SIMD.Bool32x4`)は、32-bitのブーリアン型が４つ並んでる(!)…ように一瞬見えますが、これはあくまでも **128bit単位で処理するための中間値(<cite>an intermediate value</cite>)表現**であるとされています。この場合、SIMD.Int32x4の各レーンに対しての処理を真偽値で振り分ける時などに使います。

## SIMDオブジェクト

SIMD型の頭についている`SIMD.`は`SIMD`オブジェクトという新しい標準組み込みグローバルオブジェクトです。各それぞれのデータ型をプロパティとして持つだけで、`Math`オブジェクトと同様、newしたりする事は出来ません。

```js
typeof SIMD;	//object

Object.getOwnPropertyNames(SIMD);
/*
[ 'Float32x4',
  'Int32x4',
  'Bool32x4',
  'Int16x8',
  'Bool16x8',
  'Int8x16',
  'Bool8x16',
  'Uint32x4',
  'Uint16x8',
  'Uint8x16' ]
*/
```

[`Intl`オブジェクト](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Intl)を除くとこれまでJavaScriptの標準オブジェクトにはこのような[名前空間](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93#.E3.83.97.E3.83.AD.E3.82.B0.E3.83.A9.E3.83.9F.E3.83.B3.E3.82.B0)を分けるようなものはありませんでした。その意味で、`SIMD`オブジェクトは初の標準組み込み名前空間用オブジェクトと言えるかもしれません。

ES2015の`TypedArray`はこのように名前空間で分ける事をしなかったため、データ型の数だけ似たような名前の標準組み込みオブジェクトがグローバルなスコープに散乱することになり、残念なことになっていたのでこれはES.nextの地味な進化といえるでしょう。

## 早くなる？

扱うデータサイズは限定されますが、一度の処理で4~16個扱えるので単純計算で4~16倍早くなります。

例えば要素数16の`Uint8Array`の各要素に対して処理を行うとき、これまでループで１６回繰り返し処理を行う必要があった所、`SIMD.Uint8x16`型なら１回の処理でまとめて行えます。

また、配列のように繋がったデータとしてではなく並列に並んだデータを扱えると考えると、3D計算であるような`Vector3`のようなベクトル型の行列演算などにも使えます。

 * [waifu2xのJS実装を2倍速くした例](http://inside.pixiv.net/entry/2015/07/28/230317)
 * [2~3倍早くした行列演算ライブラリgl-matrixの例](https://hacks.mozilla.org/2015/12/bringing-the-power-of-simd-js-to-gl-matrix/)

一方で既にJSの実行エンジンが内部的に最適化でSIMD命令を使用していた場合は変わりません。また、まとめて処理できるといっても大量のベクトル演算は WebGLでGPGPU or WebCL に敵いません。

あくまでも、機械に頼らず人力でJavaScriptを書く中で、CPU ＆ シングルスレッドの範囲で効率よくなるというものです。

## 扱いの制限


### 普通の演算子は使えない

SIMD型はプリミティブなデータ型ではありますが、普通の数値のように`+`や`*`などの演算子で計算できません。`add()`や`mul()`といった関数で行う事になります。


```js:NG
var a = SIMD.Int32x4.splat(0) + SIMD.Int32x4.splat(1);
//                            ^
//
//TypeError: Cannot convert a SIMD value to a number
```

```js:OK
var a = SIMD.Int32x4.add(SIMD.Int32x4.splat(0), SIMD.Int32x4.splat(1));
```


ここで長くてタイピングが面倒だからと普通にデータを抜き出して`+`などの演算子で処理してしまうと、CPUのSIMD拡張命令を経由しないので高速化の恩恵が受けられません。この制限は算術演算、ビット演算、比較演算、型のキャストなどに及びます[^2]。

既存のループコードをSIMD.jsで置き換える場合、この制限が理由で結果的に遅くなるアルゴリズムのコード[^3]を書いてしまう可能性があります。

### SIMD命令に非対応の環境がある

実行環境のCPUがSIMD命令を持たない場合、当然ながら高速化の恩恵に預かれません。PC用ではさすがにもう無いと思いますが、古めのモバイル用のCPU（Tegra2とか）やIoT用途のCPUでは対応していないということがあり得ます。

[仕様書](http://tc39.github.io/ecmascript_simd/)にはCPUが対応していない時どうしろ、とは書いていないのでJSの実行系にまかされる事になると思われます。


## TypedArrayとの関係

> 「要素数が決まっている`TypedArray`のようなもの」

と上記で書きましたが、決してTypedArrayを置き換えるものではありません。むしろ組み合わせて使う事が前提ともいえるAPIになっています。

SIMD型の各レーンの最大値は32-bit float pointです[^4]が、JavaScriptの`number`は64-bit float pointです。そのため、大きな数の`number`を何も考えずにレーンに突っ込むと桁溢れを起こしてしまいます。

SIMD型はデータ型の意識なしに扱うのは難しい形ですので、データ型を適切に扱えるTypedArray型との連携が必要になります。


## 実装状況とpolyfill

proposalのgithubリポジトリにある[polyfill](https://github.com/tc39/ecmascript_simd/blob/master/src/ecmascript_simd.js)を使用するのが現時点では無難です。

手元のNode.js v5.3では、`--harmony_simd`オプションを付ける事で有効化されましたが、
`SIMD.Uint32x4`,`SIMD.Uint16x8`,`SIMD.Uint8x16`といったデータ型が未実装、実装済みデータ型のオブジェクトでもstore/load系関数が未実装でした。


## SIMD.jsを触ってみようとする時の注意点

 * npmモジュールで公開されているpolyfillは現在の仕様に合っていない古いものです
  * [公式のpolyfill](https://github.com/tc39/ecmascript_simd/blob/master/src/ecmascript_simd.js)をDLして使いましょう
 * [MDNのドキュメント](https://developer.mozilla.org/ja-JP/docs/Web/JavaScript/SIMD_types)は、[英語版](https://developer.mozilla.org/en-US/docs/Web/JavaScript/SIMD_types)も古い提案仕様に基づいています
  * ざっくり理解には役立ちますが、APIの説明は参考にならないものがあります
 * [ES7 compat table](http://kangax.github.io/compat-table/es7/#test-SIMD_(Single_Instruction,_Multiple_Data))は記事執筆時点で最新仕様(v0.9.1)に追従しきれていません




[^1]: `Array`ではなく、Cで言うところの配列。
[^2]: それぞれの演算用の関数が用意されています。
[^3]: 既存のループ回数を単に1/4とかするとループ回数に余りがでますが、それを以前と同じ処理にするのか、新たに処理を書くのか、、、といった所に罠がありそうです。
[^4]: 策定途中では`SIMD.Float64x2`というのも検討されていたようですが、 phase 2 に先送りされたようです。