WebAssemblyとJavaScriptで浮動小数点演算の速度を比較する
December 21, 2019
WebAssemblyはバイナリをブラウザ上で直接実行するためJavaScriptより高速ですが、実際にどれくらい早いのか改めて検証してみました。今回は基本的な数値計算を扱うので、GPUを使った場合も検証します。
問題
行列計算と総和を扱います。
実装
JavaScript
配列へのアクセスを高速化するために、行列を1次元化して固定長のFloat32Array
に保存しています。
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
}
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次元化しています。
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];
}
}
}
}
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
- ブラウザ
Google Chrome 79.0.3945.79(Official Build) (64 ビット)
結果
- (1000次元・10回)
JavaScript | GPU | WebAssembly |
---|---|---|
5208ms | 2448ms |
- (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)。