niba1122.dev

WebAssemblyとJavaScriptで浮動小数点演算の速度を比較する

December 21, 2019

WebAssemblyはバイナリをブラウザ上で直接実行するためJavaScriptより高速ですが、実際にどれくらい早いのか改めて検証してみました。今回は基本的な数値計算を扱うので、GPUを使った場合も検証します。

問題

行列計算と総和を扱います。

  • C=ABC = A \cdot B
  • inai\sum_{i}^{n} a_i

実装

JavaScript

配列へのアクセスを高速化するために、行列を1次元化して固定長のFloat32Arrayに保存しています。

  • C=ABC = A \cdot B
function dotProduct(a: Float32Array, b: Float32Array, nDim: number): Float32Array {
  const c = new Float32Array(nDim**2)
  for (let k = 0; k < nDim; k++) {
    for (let i = 0; i < nDim; i++) {
      for (let j = 0; j < nDim; j++) {
        c[i * nDim + j] += a[i * nDim + k] * b[k * nDim + j]
      }
    }
  }
  return c
}
  • inai\sum_{i}^{n} a_i
function sum(values: Float32Array): number {
  const length = values.length
  let result = 0
  for (let i = 0; i < length; i++) {
    result += values[i]
  }
  return result
}

WebAssembly

JavaScriptと同様にRustで書きました。こちらも配列へのアクセスを高速化するために行列を1次元化しています。

  • C=ABC = A \cdot B
fn dot_product(dim: usize, c: &mut [f32], a: &[f32], b: &[f32]) {
    for k in 0..dim {
        for i in 0..dim {
            for j in 0..dim {
                c[i * dim + j] += a[i * dim + k] * b[k * dim + j];
            }
        }
    }
}
  • inai\sum_{i}^{n} a_i
fn sum(length: usize, values: &[f32]) -> f32 {
    let mut sum = 0.0;
    for i in 0..length {
        sum += values[i]
    }
    return sum
}

ベンチマーク

上記の計算を複数回行い、実行時間の平均値を取ります。計算に用いる値は、ベンチマークの前にランダムに生成し、各環境・各実行回で同じ値を用います。計算の速度を検証するだけなので、検証用の値はMath.random()で生成します。実行環境は以下です。

  • PC

PCのスペック

  • ブラウザ

Google Chrome 79.0.3945.79(Official Build) (64 ビット)

結果

  • C=ABC = A \cdot B (1000次元・10回)
JavaScript GPU WebAssembly
5208ms 2448ms
  • inai\sum_{i}^{n} a_i (100000個の足し合わせ・1000回)
JavaScript WebAssembly
0.1344ms 0.1018ms

参考) GPUによる並列計算(TensorFlow.js): 58.19ms

実際のコードはこちらに置いてあります。

まとめ

シンプルな計算問題でWebAssemblyとJavaScriptの速度を比較し、WebAssemblyの方が1〜2倍程度早いという結果になりました。WebAssemblyによりアプリケーションを高速化できる可能性はありますが、数値計算の高速化には依然として並列計算等のアプローチが不可欠です。TensorFlow.jsが最近WebAssemblyをサポートし始めましたが、WebAssembly上でニューラルネットワーク推論を最適化することで、GPUに近いパフォーマンスを出すことに成功しています(https://github.com/tensorflow/tfjs/tree/master/tfjs-backend-wasm)。