やまだです。 SIMD.jsについてゆるく話をします。

SIMDとは何か?

まず、SIMD(Single Instruction Multiple Data)とは何かから簡単にお話しします。 SIMDは命令1つで複数のデータの演算を一括して行う計算方式です。 複数データに対する演算を一括して行うため、 同じような演算を大量に実行しなければならない場合に威力を発揮します。 しかし、SIMDを使うためにはCPUによって異なる命令を実行する必要があります。 たとえばIntel系のCPUであればSIMD拡張の命令セットであるSSEを使い、 ARM系CPUならNEONというように使い分けないといけません。

ブラウザとSIMD

今まではこれらはJavaScriptからは使うことができませんでした。 しかし、ブラウザのパフォーマンスを求め続けた結果ある実装が生まれました。

そう、Dartです。 DartはJavaScriptとは異なる高速なVM上で動作しながらも、ECMAScript 5に変換もできるという優れた処理系でした。 この野心的なプロジェクトは1年以上前にSIMDに対応していました。 記事によると3D分野でしばしば用いられる4x4行列の乗算で300%のパフォーマンスを発揮したようです。

そして、この実装はJavaScriptにも取り入れられようとしています。 JavaScriptの標準仕様であるECMAScript 2015が承認されたことは記憶に新しいと思います。 ECMAScript 2015ではアロー演算子やデストラクチャリングなど様々な拡張が行われました。 今、ECMAScriptは次なる仕様ECMAScript 7に向けて仕様の策定が進められています。 その中の一つに今回紹介するSIMD.jsがあります。 ECMAScriptの策定プロセスのうち、Stage 2、つまりDraft段階にあり主要な機能の定義が進められています。 まだまだ仕様として安定はしていないのですが、様々なプラットフォームでSIMD.jsを試すことができます。

SIMD.jsを実際に動かしてみる

今回はChromiumでSIMD.jsのパワーを体感してみましょう。 SIMD.jsの試験的な実装がなされたChromiumは こちらから手に入ります。 Windows、Mac、Linuxそれぞれのバイナリが用意されていますが、 実行時に--js-flags=--simd-objectをコマンドライン引数として与える必要があります。

このChromiumを使用してSIMDのパフォーマンスを体感できるコードを書いてみましょう。 合計値を計算するsum関数を実装してみます。 sum1は配列を1つずつ加算して行くのに対し、sum2は4つ同時にまとめて加算しています。

//NO SIMD
var sum1 = function(list) {
  var total = 0;
  var length = list.length;
  for(var i = 0; i < length; ++i) {
    total += list[i];
  }

  return total;
}

//SIMD
var sum2 = function(list) {
  var i32x4list = new Int32x4Array(list.buffer);
  var total = SIMD.int32x4.splat(0);
  var length = list.length / 4;
  for(var i = 0; i < length; ++i) {
    total = SIMD.int32x4.add(total, i32x4list.getAt(i));
  }

  return total.x + total.y + total.z + total.w;
}
SIMD ops/s
Yes 1509ops/s
No 620ops/s

結果はご覧のとおり、2倍以上のスピードが出ています。 ただの加算ですがここまで違いが出てしまいます。 こういった大量のデータ処理はSIMDの得意分野です。

とくにWebGLではベクトル演算の需要が大いにあります。 衝突演算、物理演算に威力を発揮することでしょう。

SIMD.jsと未来

SIMDを使ったチューニングはC言語などを使い、低レイヤーで行われることが多いですが、 JavaScriptによるSIMDチューニングができる時代がすぐそこまできています。 もちろん、OSやデバイスを意識せずに手軽に使うことができます。 Mac、Windows、Linux、そしてスマートフォンでも!

工夫次第で大きくパフォーマンスを向上できるSIMD.jsの今後にワクワクしてきませんか?

@やまだ