モバイル&ワイヤレスブロードバンドでインターネットへ

gwaw.jp
 
◈ GWAW.JP / WebGPU × Finance Benchmark #1

WebGPU は本当に最速
— モンテカルロ資産シミュレーションで実測比較

1万回の独立試行を WebGPU・WebGL2・CPU の3方式で実行し、iPad mini A17 Pro と Google Pixel 10 で計測。
「WebGPU が常に最速」という思い込みを、実データで検証します。

WebGPU WebGL2 GPGPU Compute Shader Monte Carlo Benchmark

01

WebGPU は速い、という思い込み

WebGPU は次世代のブラウザ向け GPU API として注目されている。compute shader による汎用計算(GPGPU)は「速い」とされ、機械学習や物理シミュレーションでの活用が進む。

では実際のところ、WebGPU は WebGL2 や CPU よりもどれだけ速いのか。そして、常に速いのか。この素朴な問いを確かめるため、モンテカルロ資産シミュレーション(1,000〜100,000回の独立試行)を題材に、3つのバックエンドで実測した。

結論を先に書くと、答えは「条件による」だった。小規模な試行では WebGL2 のほうが速く、ある試行回数を境に WebGPU が逆転する。この「クロスオーバー現象」が、本記事のいちばん面白い発見だ。

Key Finding

WebGPU が WebGL2 を上回るのは、試行回数が約1万回を超えてから

1,000〜5,000回の小規模試行では、WebGPU の初期化オーバーヘッドが相対的に重く、WebGL2 のほうが高速だった。10,000回を境に WebGPU が逆転し、大規模試行ほど差が開く。

02

なぜモンテカルロ資産シミュレーションか

題材に選んだのは、老後資産のモンテカルロ・シミュレーションだ。毎年の運用リターンを正規分布からランダムにサンプリングし、積立・取り崩し・インフレ・暴落イベントを織り込んで、最終的に「資産が尽きる確率(破産確率)」を求める。

この計算が GPU 並列化の題材として優れているのは、各試行が完全に独立している点だ。1回のシミュレーションは他の試行の結果を一切参照しない。つまりスレッド間通信・共有メモリ・atomic 操作が不要で、GPU の「大量の単純計算を並列実行する」能力をそのまま活かせる。

同時に、これは GPGPU の「素直すぎる」ワークロードでもある。WebGPU compute shader の真価は、スレッド間で協調する複雑な計算にこそ発揮される。今回のような単純並列では、その優位性が出にくい可能性がある——という仮説も、検証の動機になった。

1試行あたりの計算内容 積立フェーズ(月次ループ)→ 取り崩しフェーズ(月次ループ・インフレ調整・破産判定)。取り崩し40年なら 1試行あたり最大で約 (20年+40年)×12 = 720 か月分の逐次計算が走る。これを数万回繰り返す。
03

3つの実装方式

同一の計算ロジック(PCG 乱数生成・Box-Muller 変換による正規分布サンプリング)を、3つの異なる実行基盤で実装した。

方式技術並列化の仕組み
WebGPUcompute shaderworkgroup_size(64) で各スレッドが1試行を担当
WebGL2transform feedback頂点シェーダーで計算し結果を頂点バッファに書き戻し
CPUJavaScript逐次ループ(バッチ処理でUIフリーズ回避)

WebGPU の compute shader は GPGPU 専用に設計されたパイプラインだ。一方 WebGL2 の transform feedback は本来は描画用の頂点シェーダーを「計算専用」に流用するテクニックで、RASTERIZER_DISCARD でラスタライズを無効化し、計算結果を頂点バッファに書き戻す。

// WGSL — WebGPU compute shader(核心部) @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) gid : vec3<u32>) { let sim = gid.x; // 各スレッド = 1試行 var state = seeds[sim]; // PCG 乱数の初期シード var asset = P.init_asset; // 積立フェーズ → 取り崩しフェーズ(月次ループ) // ... 破産判定して結果バッファに書き込み finalAssets[sim] = asset; }
乱数品質の担保 3方式すべてで PCG(Permuted Congruential Generator)を実装し、同一アルゴリズムで正規乱数を生成している。後述する破産確率が3方式で一致することが、実装の正しさを裏づける。
04

計測環境

PRIMARY DEVICE
iPad mini A17 Pro
Safari / iPadOS
Apple A17 Pro GPU
REFERENCE DEVICE
Google Pixel 10
Chrome / Firefox
Tensor G5 GPU

主データは iPad mini A17 Pro(Safari)。同一ブラウザ上でバックエンドを手動切替し、WebGPU・WebGL2・CPU を同条件で計測した。試行回数は 1,000 / 5,000 / 10,000 / 50,000 / 100,000 の5段階。シミュレーション内容は積立20年+取り崩し(5年・40年の2条件)とした。

05

計測結果 — iPad mini A17 Pro

取り崩し5年(軽負荷)の処理時間(ms)

試行回数WebGPUWebGL2CPU最速
1,000 9.0 3.0 16.0 WebGL2
5,000 4.0 3.0 53.0 WebGL2
10,000 3.0 4.0 68.0 WebGPU
50,000 8.0 15.0 328.0WebGPU
100,00014.0 22.0 WebGPU

取り崩し40年(高負荷)の処理時間(ms)

試行回数WebGPUWebGL2CPU最速
1,000 11.0 2.0 34.0 WebGL2
5,000 5.0 4.0 103.0WebGL2
10,000 6.0 7.0 190.0WebGPU
50,000 23.0 25.0 947.0WebGPU
100,00032.0 32.0 同点
CPU の 100,000回が「—」の理由 CPU は 50,000回でも 328〜947ms かかり、100,000回では実用域を超えるため計測から除外した。GPU 系が 14〜32ms で完了するのと対照的だ。
06

考察 — なぜ小規模では WebGL2 が速いのか

記事の核心はここだ。iPad mini の両条件で、1,000〜5,000回では WebGL2 が WebGPU を上回り、10,000回を境に逆転している。なぜこうなるのか。

WebGPU には固定オーバーヘッドがある

compute pipeline の生成、bind group の構築、uniform/storage バッファの確保、そして結果読み出しの mapAsync 待機。これらは試行回数によらず1回あたりに乗る固定コストで、小規模では実計算より支配的になる。1,000回で 9〜11ms かかるのに、5,000回では 4〜5ms に下がる「逆転」がその証拠だ。

モバイル GPU では描画パイプラインが成熟している

WebGL2 の transform feedback は、長年最適化されてきた頂点シェーダーパイプラインを使う。モバイル GPU(A17 Pro)では compute パイプラインより描画パイプラインのドライバ最適化が進んでいる可能性があり、小〜中規模ではこれが効く。

大規模試行では WebGPU の並列性能が勝る

10,000回を超えると、固定オーバーヘッドが相対的に小さくなり、純粋な並列演算性能が支配的になる。WebGPU は試行回数が100倍(1,000→100,000)になっても 9ms→14ms とほぼ横ばいで、優れたスケーラビリティを示した。

Observation

高負荷条件では WebGPU と WebGL2 が拮抗する

取り崩し40年(高負荷)の 100,000回では、WebGPU・WebGL2 ともに 32ms で並んだ。1試行あたりの計算量が増えると、両 GPU バックエンドとも「準備オーバーヘッド」より「実計算」が支配的になり、純粋な演算性能で拮抗するためと考えられる。

もう一点、参考機の Google Pixel 10(Tensor G5)では、10,000回時点で WebGL2(33ms) が WebGPU(47ms) を上回った。クロスオーバーポイントはデバイス・GPU によって異なるのだ。これは「どのバックエンドが速いか」が単純な優劣ではなく、ハードウェアと規模の組み合わせで決まることを意味する。

07

結果の妥当性 — 破産確率の一致

速度だけでなく、計算結果そのものの一貫性も確認した。同一パラメータでの破産確率は、3つの実装で一致した。

バックエンド破産確率(10,000回)
WebGPU56.2 %
WebGL255.9 %

異なる実行基盤・異なる乱数シードでも、破産確率は誤差0.3ポイント以内で一致した。これはモンテカルロ法が十分な試行回数で収束していること、そして3実装の計算ロジックが等価であることの両方を裏づける。速度比較の前提となる「同じ計算をしている」という条件が満たされている。

08

どのバックエンドを選ぶべきか

状況推奨
小〜中規模・単純並列WebGL2 で十分(対応環境も広い)
大規模試行・継続的な再計算WebGPU(スケーラビリティが効く)
スレッド協調・atomic が必要WebGPU(compute の本領)
互換性最優先CPU フォールバック必須
現実的な結論:3段階フォールバック WebGPU が常に最速とは限らない以上、「WebGPU が使えれば WebGPU、なければ WebGL2、それもなければ CPU」という3段階フォールバックを実装するのが現実解だ。本デモもこの構造を採用している。どの環境でも動き、かつ各環境で最適に近いバックエンドが選ばれる。
09

実装の落とし穴

実装中に遭遇した、再現性のあるハマりどころを共有する。

  • Firefox の Transform Feedback バッファ競合:出力バッファを ARRAY_BUFFER にバインドしたまま読み出すとエラーになる。VAO で属性状態を分離し、読み出し前に bindBufferBase(..., null) で解放、さらに gl.flush() で完了を保証する必要がある。これを怠ると全件0が返る。
  • WebGPU の uniform バッファレイアウト:u32 と f32 が混在する構造体は、std140 に近いアライメントを意識して同一 ArrayBuffer 上に詰める。ズレると意味不明な計算結果になる。
  • 全件0問題の検出:GPGPU が無音で失敗すると全件0(=即破産)になり、破産確率が100%に化ける。結果の非ゼロ件数を検証し、異常時は CPU にフォールバックする安全弁を入れた。
// WebGL2 transform feedback — Firefox 対策の核心 gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf); gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, faBuf); // ... drawArrays(RASTERIZER_DISCARD 下で計算)... gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null); // 解放が必須 gl.flush(); // 完了を保証 gl.getBufferSubData(gl.ARRAY_BUFFER, 0, finalAssets);
10

まとめ

  • WebGPU は常に最速ではない。小規模試行では初期化オーバーヘッドにより WebGL2 に負ける。
  • クロスオーバーポイントが存在する。iPad mini A17 Pro では約10,000回を境に WebGPU が逆転した。
  • クロスオーバーはデバイス依存。Pixel 10 では傾向が異なり、ハードウェアごとに最適点が変わる。
  • CPU 比は GPU 系で 10〜40倍。GPGPU の価値そのものは、どの規模でも明確だった。
  • 結果は3実装で一致。破産確率 56% 前後で揃い、速度比較の前提が担保された。

「GPU を使えば速い」という直感は、半分正しく半分間違っている。GPGPU は CPU に対して圧倒的だが、WebGPU と WebGL2 のどちらが速いかは、ワークロードの規模・性質とハードウェアの組み合わせで決まる。最速を1つ選ぶのではなく、3段階フォールバックで「その環境での最適」を自動選択するのが、実務的にもっとも筋が良い。

実際に試せるインタラクティブ・デモ

本記事のベンチマークは、ブラウザ上でそのまま再現できます。お手元のデバイスでクロスオーバーポイントを探してみてください。

▶ モンテカルロ資産シミュレーター を開く
⚠ 注意事項 本記事の計測値は特定の端末・ブラウザ・実行時条件における参考値です。同一端末でも実行のたびに数ms変動します。また、資産シミュレーション部分は教育目的のモデルであり、実際の投資判断の根拠としての使用はお控えください。

『WebGPU は本当に最速か — モンテカルロ資産シミュレーションで実測比較』を公開しました。