なにしたの?
WebAssembly と JavaScript の波動方程式のシミュレーション速度を比較してみました。
↓のリンク先で試してみて下さい。中央から波が広がっていく様子が描かれます。
左側が JavaScript での実装、右側が WebAssembly の実装で、WebAssembly の方が少しばかしシミュレーションが速くなるはず。
WebAssembly(ウェブアセンブリー)は、ウェブブラウザ上で高速に動作するバイナリコードの仕様で、C、C++、Rust などの言語からコンパイルできるため、JavaScript だけでは処理しきれない計算集約的な処理や、パフォーマンスを要求されるブラウザゲーム、音声・画像処理などに利用されます。(Google の AI による概要)
なんでいまさら WebAssembly で波動方程式?
ある程度計算負荷があればなんでもよかったんですが、動きが直感的にわかりやすいのが良いかなと思い、波動方程式の数値計算を対象にしています。
C や C++ からコンパイルできるなら、MATLAB から WebAssembly もできるじゃないか、ということで興味を持ったわけです。そう、MATLAB Coder で C コードに変換してから WebAssembly 化します。
C コード生成に対応している処理であれば MATLAB の処理がそのまま JavaScript に統合できるってわけです。MATLAB でサクッと書いたアルゴリズムが WebAssembly に自動変換できて速度も JavaScript より速いなら面白いかも?と考えました。
JavaScript と WebAssembly のベンチマークについてはこちらに秀逸な repo がありましたので、興味のある方は見てみてください。意外(?)と WebAssembly の方が必ずしも速いわけではないのも分かります。
GitHub: WebAssembly-banchmark
以前やっていたこと
以前は GenerateJavaScriptUsingMATLABCoder, Geoff McVittie (2021) を使いましたが、このツールも 2021 年からアップデートされなくなってしまったので、使わずに実施する方法を見つけておきたかったのも理由です。
昔の投稿は↓
- 数独の楽しみが台無し!写真を撮って解けるようにしてみた話
- MATLAB のニューラルネットをブラウザで動かす: MATLAB > C++ > WebAssembly の自動変換
- MATLAB -> C++ -> WebAssembly の自動変換を使った非線形最適化 on JavaScript
今回やったことは?
- 波動方程式を解く MATLAB コード作成
- C コードに変換、WebAssembly にコンパイル
- 波動方程式を解く JavaScript コードと UI 周り(HTML)を作成
- WebAssembly を呼び出す部分を実装
JavaScript や HTML 周りはふだん触らないので ChatGPT とデバッグしながら進めました。
順番に紹介していきます。
環境
- MacOS X Sonoma 14.4.1
- MATLAB R2025b (MATLAB, MATLAB Coder)
- emcc 4.0.16
1. 波動方程式を解く MATLAB コード作成
これはシンプルですね。波動方程式の時間ステップを 1 つ進める、すなわち 1 つ先の波の状態を求める関数にします。
function u_next = wave2D_step(u, u_prev, c, dt, dx, dy, damping)
2 ステップ分の解から次のステップを計算します
関数の中身:興味あれば見てみて
function u_next = wave2D_step(u, u_prev, c, dt, dx, dy, damping)
% 2D Wave equation step
% u : current wave state [ny x nx]
% u_prev : previous wave state [ny x nx]
% c, dt, dx, dy : wave parameters
% damping : damping factor
%
% returns:
% u_next : next wave state [ny x nx]
% u : current becomes previous
% u_prev : previous becomes current
[ny, nx] = size(u);
u_next = zeros(ny, nx);
iddx = 1/(dx^2);
iddy = 1/(dy^2);
ccddt = c^2*dt^2;
for i = 2:nx-1
for j = 2:ny-1
lap = (u(j,i+1) - 2*u(j,i) + u(j,i-1))*iddx + ...
(u(j+1,i) - 2*u(j,i) + u(j-1,i))*iddy;
u_next(j,i) = 2*u(j,i) - u_prev(j,i) + ccddt*lap;
u_next(j,i) = u_next(j,i) * damping;
end
end
% 固定境界条件
u_next(1,:) = 0;
u_next(ny,:) = 0;
u_next(:,1) = 0;
u_next(:,nx) = 0;
end
GitHub: Simple-Wave-Equation-solver にも波動方程式の解法に関する簡単な解説&コードを公開していますが、細かいところはここでは省きます。
↓こんな風に波が移動していく結果になるはずです(イメージ図)
2. C コードに変換、WebAssembly にコンパイル
ここからが本題です。MATLAB で書いた wave2D_step 関数を C コード経由で WebAssembly 化していきます。
(1) MATLAB Coder で C コード生成
MATLAB のアプリで作業します。以下の手順を行います。
- MATLAB Coder アプリを開く
- 対象関数に
wave2D_step.mを指定 - 入力引数サイズや変数型を指定(下図)
- 生成言語に C を選択
- ビルドすると
wave2D_step.c、wave2D_step.hなどのコード一式が出てきます
WASM で OpenMP がサポートされないようので、"Enable OpenMP Library if possible" のチェックを外しておきましょう。
MATLAB コードでやるならこんな感じ(アプリからコード生成させました)。EnableOpenMP = false になってますね。
折りたたんでいます
% WAVE2D_SCRIPT Generate static library wave2D_step from wave2D_step.
%
% Script generated from project 'wave2D.coderprj' on 01-Dec-2025.
%
% See also CODER, CODER.CONFIG, CODER.TYPEOF, CODEGEN.
%% Create configuration object of class 'coder.CodeConfig'.
cfg = coder.config("lib", "ecoder", false);
cfg.EnableOpenMP = false;
%% Define argument types for entry-point 'wave2D_step'.
inputTypes = cell(1, 1);
inputTypes{1} = cell(7, 1);
inputTypes{1}{1} = coder.newtype("double", [Inf Inf], [1 1]);
inputTypes{1}{2} = coder.newtype("double", [Inf Inf], [1 1]);
inputTypes{1}{3} = coder.newtype("double", [1 1], [0 0]);
inputTypes{1}{4} = coder.newtype("double", [1 1], [0 0]);
inputTypes{1}{5} = coder.newtype("double", [1 1], [0 0]);
inputTypes{1}{6} = coder.newtype("double", [1 1], [0 0]);
inputTypes{1}{7} = coder.newtype("double", [1 1], [0 0]);
%% Invoke MATLAB Coder.
codegen -config cfg wave2D_step -args inputTypes{1}
(2) MacOS 上で Emscripten をセットアップ
WebAssembly に変換するには Emscripten が必要です。
以下は macOS 用の最短セットアップ手順です。
# Emscripten SDK の取得
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
# 最新版をインストール
./emsdk install latest
# 有効化
./emsdk activate latest
# PATH を通す
source ./emsdk_env.sh
正しくインストールされているか確認:
emcc -v
バージョン情報が表示されれば成功です。
(3) C コード → WebAssembly にコンパイル
MATLAB Coder が出力した wave2D_step.c 一式を WebAssembly に変換します。ChatGPT との試行錯誤の末行き着いたコマンドオプションはこちら!
emcc wave2D_step.c wave2D_step_emxAPI.c wave2D_step_emxutil.c \
wave2D_step_initialize.c wave2D_step_terminate.c \
-O3 \
-s MODULARIZE=0 -s EXPORT_ES6=0 \
-s 'EXPORTED_FUNCTIONS=["_wave2D_step","_wave2D_step_initialize","_wave2D_step_terminate","_emxCreateND_real_T","_emxDestroyArray_real_T","_malloc","_free"]' \
-s 'EXPORTED_RUNTIME_METHODS=["getValue","HEAP32","HEAPF64"]' \
-I/Applications/MATLAB_R2025b.app/extern/include \
-o wave2D_step.js
-
wave2D.wasm(実体) -
wave2D.js(WASM ローダー)
の 2 ファイルが生成されます。それぞれのオプションの意図としては以下の通りです。
| 分類 | オプション | 役割 |
|---|---|---|
| 最適化 | -O3 |
WebAssembly 向けの最高レベル最適化。ループ展開・SIMD 化などが適用され、数値計算コードの性能を最大化する。特に PDE 系(波動方程式)のような反復計算では大きな効果がある。 |
| 出力形式 | MODULARIZE=0 |
JS をモジュール化しない。グローバル Module オブジェクトを生成し、Module._wave2D_step() のように直接関数呼び出しできる構成になる。ブラウザで扱いやすい。 |
| 出力形式 | EXPORT_ES6=0 |
ES6 Modules を無効化し、<script> タグだけで読み込める古典的形式にする。WASM を CDN 経由やローカルファイルで手早く動かしたい場合に便利。 |
| 関数 export | EXPORTED_FUNCTIONS |
WASM 内部関数のうち JavaScript から利用したいものを明示的に公開する。今回は波動計算本体 _wave2D_step、初期化/終了処理、および MATLAB Coder の emxArray 用 API、さらには malloc / free を選択。 |
| ランタイム export | EXPORTED_RUNTIME_METHODS |
WASM メモリアクセスのため |
| MATLAB 依存 | -I ... extern/include |
MATLAB Coder が生成する C コード (emxArray など) が必要とするヘッダーファイルを参照させるための include パス。 |
| 出力 | -o wave2D_step.js |
JavaScript スタブファイル名。実行時に wave2D_step.wasm が自動ロードされる。ブラウザ側で Module._wave2D_step() として呼び出せるようになる。 |
ここまでで MATLAB → C → WebAssembly の変換は完了。あとはこれをブラウザ側で呼び出すだけです。
3. 波動方程式を解く JavaScript コードと UI 周り(HTML)を作成
WebAssembly 単体では動作しないので、JavaScript で UI と描画まわりを作ります。HTML 側では canvas を 2 枚配置し、左に JavaScript 実装、右に WebAssembly 実装の結果を描画する構成にしました。JavaScript での波動方程式解法は ChatGPT に書いてもらいましたが、MATLAB 側の実装と同等になるようしています。
コードはこちら:waveEquation.html
JavaScript 側では
- 波の配列初期化
- 描画ループ
- canvas への可視化
- 各ステップの処理速度の計測
などさせています。WASM 実装に比べ、JavaScript 側のコードは素直です。
4. WebAssembly を呼び出す部分を追加
ここが今回の肝で、JavaScript と WebAssembly 間のデータ受け渡しが少しコツ要ります。
C で生成された emxArray_real_T を WebAssembly 上に確保し、JavaScript 側から直接アクセスできるように初期化している部分です。MATLAB Coder が出力する可変長配列構造 emxArray を使うために必要なステップとなります。
// dimsPtr に配列サイズ情報を書き込む
const dimsPtr = Module._malloc(2 * 4);
Module.HEAP32[dimsPtr >> 2] = nx;
Module.HEAP32[(dimsPtr >> 2) + 1] = ny;
// WebAssembly 側に配列を確保
wasmState.u = Module._emxCreateND_real_T(2, dimsPtr);
wasmState.u_prev = Module._emxCreateND_real_T(2, dimsPtr);
wasmState.u_next = Module._emxCreateND_real_T(2, dimsPtr);
Module._free(dimsPtr);
emxCreateND_real_T は多次元配列を作成する API で、その際に次元数と各次元のサイズを指すポインタが必要になります。今回は 2 次元配列なので 2 * 4 バイト(32bit × 2 要素)を確保し、nx, ny を書き込みます。生成後は dimsPtr が不要になるため _free で解放しておきます。
// 配列の先頭アドレスを取得
const u_ptr = Module.getValue(wasmState.u, '*');
const u_prev_ptr = Module.getValue(wasmState.u_prev, '*');
const u_next_ptr = Module.getValue(wasmState.u_next, '*');
u, u_prev, u_next の 3 つの 2D 配列を WASM ヒープ領域に作ります。emxArray は構造体なので、実際のデータが格納されている領域のポインタを getValue で取り出す必要ある点に注意。ここで取得したポインタが、実際の数値データ(double)が入る先頭アドレスです。
// 初期値の書き込みとゼロ初期化
Module.HEAPF64.set(temp_u, u_ptr / 8);
Module.HEAPF64.fill(0, u_prev_ptr / 8, u_prev_ptr / 8 + (nx * ny));
Module.HEAPF64.fill(0, u_next_ptr / 8, u_next_ptr / 8 + (nx * ny));
- u に初期波形
temp_uをコピー -
u_prev,u_nextは計算開始前状態なので 0 クリア - / 8 しているのは、double が 8 バイトだから
そして wave2D_step 関数を呼び出す部分はこんな具合です。1 step 進めて、1, 2 ステップ前の値を参照できるようにコピーしておきます。
Module._wave2D_step(wasmState.u, wasmState.u_prev, c, dt, dx, dy, damping, wasmState.u_next);
const tmp = wasmState.u;
wasmState.u = wasmState.u_next;
wasmState.u_next = wasmState.u_prev;
wasmState.u_prev = tmp;
canvas に描くためには値を取得します。
const u_ptr = Module.getValue(wasmState.u, '*');
wasmCanvasView = new Float64Array(Module.HEAPF64.buffer, u_ptr, nx * ny);
結果としてできたのがこちらです。
今回は JavaScript 実装と比較して WebAssembly 実装の方が軽快に動くことが確認できました。シミュレーションサイズが大きいほど WebAssembly 優位になる傾向あるようです。
まとめ
「ブラウザ上で処理したいけど、JavaScript で書くのは大変そう。MATLAB なら関数あるのに・・」そんなシチュエーションにでくわしても、(もし出くわしたらあったら教えて下さい!)MATLAB で書いて JavaScript から呼び出す選択肢があることが実証できました。
- JavaScript は便利だが、数値計算はしんどい
- MATLAB ならアルゴリズムが速攻で書ける
- C → WebAssembly というルートが確立できれば、両者のいいとこ取りができる(かも)
もちろん、WebAssembly が常に JavaScript より速いわけではありません。特に小さな配列や軽い処理だと、呼び出しコストで WASM が負けることもあります。今回 JavaScript 側の実装もなんらか速くするテクニックがありそうな気もします。
しかし今回の例のように、
- 計算コストが重く、UI と分離できる処理
では、WebAssembly が有効な選択肢となると思います。



