【JavaScript】限界を超える?WebAssembly(Wasm)の可能性

Web開発の世界は常に進化しており、JavaScriptはその中心的な役割を担ってきました。しかし、より高度で複雑な処理、例えば3Dグラフィックス、ゲーム、動画編集などをWebブラウザ上で実現しようとすると、JavaScriptのパフォーマンスに限界が見えてくることがあります。
そこで注目されているのが WebAssembly(Wasm) です。
この記事では、「WebAssemblyって何?」「どんなメリットがあるの?」「どうやって始めるの?」といった疑問に、初心者の方にも分かりやすくお答えします。
WebAssembly(Wasm)とは?
WebAssembly(ウェブアセンブリ)は、Webブラウザで実行できる新しいタイプのコードです。テキストベースのJavaScriptとは異なり、Wasmはバイナリ形式(コンピュータが直接理解しやすい形式)になっています。
主な特徴は以下の通りです。
- 高速:バイナリ形式のため、JavaScriptよりも高速に実行されることが期待できます。特に計算量の多い処理で効果を発揮します。
- 効率的:コンパクトなバイナリ形式なので、ダウンロードサイズが小さく、読み込みも速いです。
- 安全:Webブラウザのセキュリティサンドボックス内で実行されるため、安全性が確保されています。
- 言語非依存:C、C++、Rust、Goなど、様々なプログラミング言語からWasmにコンパイルできます。つまり、使い慣れた言語で書いたコードをWebで動かすことが可能になります。
- JavaScriptとの連携:WasmはJavaScriptを置き換えるものではなく、JavaScriptと連携して動作します。Wasmで高速な処理を行い、JavaScriptでDOM操作やWeb APIの呼び出しを行う、といった使い分けが可能です。
なぜWasmが必要なのか?
JavaScriptは非常に強力で柔軟な言語ですが、以下のような課題も抱えています。
- パフォーマンス:大規模で計算量の多い処理(ゲームエンジン、動画エンコードなど)では、実行速度がボトルネックになることがあります。
- 言語:Webフロントエンド開発は、事実上JavaScript(またはTypeScriptなどJavaScriptにコンパイルされる言語)が主流です。他の言語で書かれた既存の資産(ライブラリなど)をWebで活かしにくい側面がありました。
Wasmはこれらの課題を解決する可能性を秘めています。C/C++やRustといった言語で書かれた高性能なコードをWebブラウザ上で安全かつ高速に実行できるため、これまで難しかったアプリケーションの開発が可能になります。
Wasmのメリット・デメリット
メリット
- 高速な実行速度:CPU負荷の高い処理を高速化できます。
- 言語選択の自由度:C/C++、Rust、Goなど、様々な言語を利用できます。
- 既存コードの活用:デスクトップ向けなどに書かれたC/C++ライブラリなどをWebで再利用しやすくなります。
- 安全性:ブラウザのサンドボックス内で動作します。
- JavaScriptとの共存:JavaScriptと連携し、それぞれの得意な分野を活かせます。
デメリット/注意点
- DOM操作: Wasmから直接HTMLの要素(DOM)を操作することはできません。DOM操作はJavaScriptを経由して行う必要があります。
- デバッグ:JavaScriptに比べると、デバッグ環境がまだ発展途上の部分があります。
- エコシステム:JavaScriptほど巨大なエコシステム(ライブラリ、フレームワーク、ツール)はまだありませんが、急速に成長しています。
- 学習コスト:新しい技術スタック(コンパイルツールなど)の学習が必要になる場合があります。
Wasmを使ってみよう(始め方)
実際にWasmを体験してみましょう。ここでは、簡単なC言語のコードをWasmにコンパイルし、JavaScriptから呼び出す例を紹介します。
準備するもの
- C/C++コンパイラ:Wasmを出力できるコンパイラが必要です。ここでは Emscripten というツールを使います。
- Emscriptenのインストール方法は公式サイト(https://emscripten.org/docs/getting_started/downloads.html)を参照してください。OSごとに手順が異なります。
- テキストエディタ:コードを書くためのエディタ(VS Codeなど)。
- Webブラウザ:Wasmを実行するためのブラウザ(Chrome, Firefox, Safari, Edgeなど、最近の主要ブラウザは対応しています)。
- ローカルWebサーバー:セキュリティ上の理由から、Wasmファイルを直接ファイルとして開くのではなく、ローカルサーバー経由でアクセスする必要があります。Node.jsの http-server や Python の SimpleHTTPServer などで簡単に立てられます。
ステップ1:C言語のコードを作成(add.c)
まず、2つの数値を足し算する簡単なC言語の関数を作成します。
// add.c
#include <emscripten.h> // Emscripten用のヘッダーファイルをインクルード
EMSCRIPTEN_KEEPALIVE // この関数をJavaScriptから呼び出せるようにするおまじない
int add(int a, int b) {
return a + b;
}- #include <emscripten.h>:Emscriptenが提供する機能を使うために必要です。
- EMSCRIPTEN_KEEPALIVE:コンパイル時にこの add 関数が不要だと判断されて削除されないように、「この関数は外部(JavaScript)から使うから残しておいてね」とコンパイラに伝えるための記述(マクロ)です。
ステップ2: EmscriptenでWasmにコンパイル
ターミナル(コマンドプロンプト)を開き、add.c があるディレクトリで以下のコマンドを実行します。
emcc add.c -o add.js -s WASM=1 -s EXPORTED_FUNCTIONS="['_add']" -s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap']"- emcc:Emscriptenのコンパイラコマンドです。
- add.c:コンパイルするソースファイル。
- -o add.js:出力ファイル名を指定します。add.wasm というWasmファイルと、それを読み込むためのJavaScriptファイル(add.js)が生成されます。
- -s WASM=1:Wasmを出力するオプションです(最近のEmscriptenではデフォルトで有効な場合もあります)。
- -s EXPORTED_FUNCTIONS="['_add']":JavaScriptから呼び出したいC言語の関数名を指定します。関数名の前にアンダースコア _ が付きます。
- -s EXTRA_EXPORTED_RUNTIME_METHODS="['cwrap']":JavaScriptからCの関数を簡単に呼び出すためのラッパー関数 cwrap を使えるようにするオプションです。
このコマンドを実行すると、カレントディレクトリに add.js と add.wasm が生成されます。
ステップ3:HTMLとJavaScriptを作成
Wasmモジュールを読み込んで実行するためのHTMLファイル(index.html)とJavaScriptファイル(script.js)を作成します。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Wasm Add Example</title>
</head>
<body>
<h1>WebAssembly 足し算サンプル</h1>
<p>
<input type="number" id="num1" value="5"> +
<input type="number" id="num2" value="7"> =
<span id="result">?</span>
</p>
<button id="calculateButton">計算する</button>
<script src="add.js"></script>
<script src="script.js"></script>
</body>
</html>script.js
// script.js
// Emscriptenのランタイムが初期化された後に実行される関数
Module.onRuntimeInitialized = function() {
console.log('Wasmモジュールが読み込まれました。');
// Cの'add'関数をJavaScriptから呼び出せるようにラップする
// 第1引数: Cの関数名(アンダースコア付き)
// 第2引数: 戻り値の型 ('number', 'string', 'array', 'boolean', null)
// 第3引数: 引数の型の配列
const add = Module.cwrap('add', 'number', ['number', 'number']);
const num1Input = document.getElementById('num1');
const num2Input = document.getElementById('num2');
const resultSpan = document.getElementById('result');
const calculateButton = document.getElementById('calculateButton');
calculateButton.addEventListener('click', () => {
const num1 = parseInt(num1Input.value, 10);
const num2 = parseInt(num2Input.value, 10);
// ラップした関数を呼び出す
const sum = add(num1, num2);
resultSpan.textContent = sum;
console.log(`${num1} + ${num2} = ${sum}`);
});
// 初期計算
calculateButton.click();
};
console.log('Wasmモジュールを読み込み中...');- add.js の読み込み:HTMLファイルで、Emscriptenが生成した add.js を先に読み込みます。このファイルが add.wasm の読み込みや初期化処理を行います。
- Module.onRuntimeInitialized:add.js がWasmモジュールの読み込みと初期化を完了すると、Module オブジェクトの onRuntimeInitialized プロパティに設定された関数が呼び出されます。Wasm関数を呼び出す処理は、この中(またはこれ以降)に書く必要があります。
- Module.cwrap:Emscriptenが提供する便利な関数です。C言語の関数を型情報と共にラップし、通常のJavaScript関数のように呼び出せるようにします。
- 'add' :C言語での関数名 add を指定します(Emscriptenの内部では _add として扱われますが、cwrap ではアンダースコアなしで指定します)。
- 'number' :C言語の int add() の戻り値の型 int が、JavaScriptの number 型に対応することを指定します。
- ['number', 'number'] :C言語の add(int a, int b) の引数の型 int, int が、JavaScriptの number, number 型に対応することを指定します。
- イベントリスナー:ボタンがクリックされたら、入力欄から数値を取得し、cwrap で作成した add 関数を呼び出して結果を表示します。
ステップ4: ローカルサーバーで確認
- ターミナルで add.c, add.js, add.wasm, index.html, script.js があるディレクトリに移動します。
- ローカルWebサーバーを起動します。
- Node.js がインストールされている場合:npx http-server .
- Python 3 がインストールされている場合:python -m http.server
- Webブラウザを開き、サーバーが指定したアドレス(例:http://localhost:8080 や http://localhost:8000)にアクセスします。
ブラウザに足し算のインターフェースが表示され、「計算する」ボタンを押すとWasmで計算された結果が表示されれば成功です!開発者ツール(コンソール)にもログが表示されているはずです。
有名サービスの裏側を覗く:Wasm活用サンプルコード
WebAssemblyは、Google Earth、AutoCAD Web App、Figma、Photoshop Web版といった、高度な処理を行う多くのWebサービスで実際に活用されています。これらのサービスでは、グラフィックス処理、計算処理、データ処理など、パフォーマンスが要求される部分にWasmが使われています。
ここでは、それらのサービスで使われている技術の一部をイメージした、簡単なサンプルコードを見ていきましょう。
サンプル1: 画像のグレースケール化(C言語 + Emscripten)
FigmaやPhotoshopのようなグラフィックツールでは、画像のフィルタ処理などにWasmが活用されている可能性があります。ここでは、C言語で画像のグレースケール化処理を実装し、JavaScriptから利用する例を見てみましょう。
ステップ1:C言語のコード(grayscale.c)
画像のピクセルデータ(RGBAの配列)を受け取り、グレースケールに変換する関数を作成します。
// grayscale.c
#include <emscripten.h>
#include <stdlib.h> // malloc, free を使うため
// EMSCRIPTEN_KEEPALIVE: JavaScriptから呼び出すために必要
// _grayscale: JavaScriptから呼び出す際の関数名(アンダースコア付きになる)
EMSCRIPTEN_KEEPALIVE
unsigned char* grayscale(unsigned char* img_data, int width, int height) {
int size = width * height * 4; // RGBAなのでピクセル数 * 4
// 処理結果を格納するためのメモリを確保
unsigned char* result_data = (unsigned char*)malloc(size * sizeof(unsigned char));
if (!result_data) {
return NULL; // メモリ確保失敗
}
for (int i = 0; i < size; i += 4) {
// 簡単なグレースケール計算 (R, G, Bの平均値)
unsigned char gray = (img_data[i] + img_data[i+1] + img_data[i+2]) / 3;
result_data[i] = gray; // R
result_data[i+1] = gray; // G
result_data[i+2] = gray; // B
result_data[i+3] = img_data[i+3]; // Alpha (透明度) はそのまま
}
// 確保したメモリ領域のポインタを返す
return result_data;
}
// JavaScript側でメモリを解放するための関数
EMSCRIPTEN_KEEPALIVE
void free_memory(void* ptr) {
free(ptr);
}- ピクセルデータは unsigned char の配列として扱います(RGBAの順)。
- malloc でWasmモジュール内のメモリ(ヒープ)に結果格納用の領域を確保します。
- グレースケール計算を行い、結果を result_data に書き込みます。
- 確保したメモリのポインタを返します。JavaScript側でこのポインタを使ってデータにアクセスします。
- free_memory 関数は、JavaScript側で使い終わったWasm内のメモリを解放するために用意します。Wasmで確保したメモリは、不要になったら明示的に解放することが重要です。
ステップ2:コンパイル
# ALLOW_MEMORY_GROWTH=1 は、必要に応じてWasmが使用するメモリを増やせるようにするオプション
emcc grayscale.c -o grayscale.js -s WASM=1 -s EXPORTED_FUNCTIONS="['_grayscale', '_free_memory']" -s EXTRA_EXPORTED_RUNTIME_METHODS="['ccall', 'setValue', 'getValue', 'HEAPU8']" -s ALLOW_MEMORY_GROWTH=1- EXPORTED_FUNCTIONS:_grayscale と _free_memory をエクスポートします。
- EXTRA_EXPORTED_RUNTIME_METHODS:JavaScriptからWasmのメモリを操作したり、関数を呼び出したりするためのメソッド(ccall など)を有効にします。HEAPU8 はWasmのメモリに直接アクセスするためのUint8Arrayビューです。
- ALLOW_MEMORY_GROWTH=1:大きな画像を処理する場合など、実行時にメモリが足りなくなる可能性に備えて、メモリの動的確保を許可します。
ステップ3:HTML(index_image.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Wasm Grayscale Example</title>
<style> canvas { border: 1px solid black; margin: 10px; } </style>
</head>
<body>
<h1>Wasm 画像グレースケール化サンプル</h1>
<input type="file" id="imageLoader" name="imageLoader"/>
<div>
<canvas id="canvasOriginal"></canvas>
<canvas id="canvasResult"></canvas>
</div>
<script src="grayscale.js"></script>
<script src="script_image.js"></script>
</body>
</html>ステップ4:JavaScript(script_image.js)
// script_image.js
const imageLoader = document.getElementById('imageLoader');
const canvasOriginal = document.getElementById('canvasOriginal');
const ctxOriginal = canvasOriginal.getContext('2d');
const canvasResult = document.getElementById('canvasResult');
const ctxResult = canvasResult.getContext('2d');
let grayscaleWasm = null;
let freeMemoryWasm = null;
// Wasmモジュールが準備できたら関数をセット
Module.onRuntimeInitialized = () => {
console.log('Wasmモジュール読み込み完了');
// ccallを使ってCの関数を呼び出す準備 (より低レベルな方法)
// grayscaleWasm = Module.cwrap('grayscale', 'number', ['number', 'number', 'number']);
// freeMemoryWasm = Module.cwrap('free_memory', null, ['number']);
// _grayscale, _free_memory を直接使う場合は cwrap は不要だが、メモリ操作が煩雑になる
};
imageLoader.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
// 元画像をCanvasに描画
canvasOriginal.width = img.width;
canvasOriginal.height = img.height;
ctxOriginal.drawImage(img, 0, 0);
// グレースケール処理を実行
processImageGrayscale(img.width, img.height);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
function processImageGrayscale(width, height) {
if (!Module.calledRun) { // まだRuntimeが初期化されていない場合
console.warn("Wasm ランタイムが初期化されていません。");
return;
}
console.log("グレースケール処理を開始...");
// 1. 元画像のピクセルデータを取得
const imageData = ctxOriginal.getImageData(0, 0, width, height);
const pixelData = imageData.data; // Uint8ClampedArray
const dataSize = pixelData.length * pixelData.BYTES_PER_ELEMENT; // バイト単位のサイズ
// 2. Wasmのメモリに入力データ用の領域を確保し、データをコピー
const inputPtr = Module._malloc(dataSize); // _mallocでメモリ確保
if (!inputPtr) {
console.error("Wasm メモリの確保に失敗 (input)");
return;
}
// HEAPU8ビューを使ってJavaScriptの配列データをWasmのメモリに書き込む
Module.HEAPU8.set(pixelData, inputPtr);
console.log(`入力データ(${dataSize}bytes)をWasmメモリ (ptr: ${inputPtr}) にコピー`);
// 3. Wasmのgrayscale関数を呼び出す (_ccallを使う例)
// const resultPtr = grayscaleWasm(inputPtr, width, height); // cwrapを使う場合
const resultPtr = Module._grayscale(inputPtr, width, height); // 直接呼び出す場合 (戻り値はポインタ)
console.log(`Wasm関数 _grayscale 実行完了。結果ポインタ: ${resultPtr}`);
if (!resultPtr) {
console.error("Wasm関数がNULLポインタを返しました。メモリ確保失敗の可能性。");
Module._free(inputPtr); // 入力用に確保したメモリを解放
return;
}
// 4. Wasmのメモリから結果データを読み出す
const resultPixelData = new Uint8ClampedArray(dataSize);
// HEAPU8ビューを使ってWasmメモリ (resultPtrからdataSizeバイト分) を読み出す
resultPixelData.set(Module.HEAPU8.subarray(resultPtr, resultPtr + dataSize));
console.log(`結果データをWasmメモリ (ptr: ${resultPtr}) から読み出し`);
// 5. 結果をCanvasに描画
canvasResult.width = width;
canvasResult.height = height;
const resultImageData = new ImageData(resultPixelData, width, height);
ctxResult.putImageData(resultImageData, 0, 0);
console.log("結果をCanvasに描画");
// 6. Wasmで確保したメモリを解放
console.log(`Wasmメモリを解放: inputPtr=${inputPtr}, resultPtr=${resultPtr}`);
Module._free(inputPtr);
// Module._free(resultPtr); // C側で確保したメモリは、C側で解放する関数を呼ぶ
Module._free_memory(resultPtr); // 用意したfree_memoryを呼び出す
// freeMemoryWasm(resultPtr); // cwrapを使う場合
console.log("グレースケール処理完了");
}
// --- メモリ操作に関する補足 ---
// Module._malloc(size): Wasmのメモリ内に指定したバイト数の領域を確保し、その先頭ポインタ(数値)を返す
// Module._free(ptr): _mallocで確保したメモリ領域を解放する
// Module.HEAPU8: Wasmのメモリ全体を表すUint8Arrayビュー。これを使うとJavaScriptから直接Wasmのメモリを読み書きできる
// Module.HEAPU8.set(sourceArray, targetPtr): sourceArrayの内容を、WasmメモリのtargetPtrの位置に書き込む
// Module.HEAPU8.subarray(startPtr, endPtr): WasmメモリのstartPtrからendPtrまでの部分配列ビューを取得するポイント
- getImageData でCanvasからピクセルデータを取得します(Uint8ClampedArray)。
- Module._malloc でWasmモジュール内にデータを置くためのメモリ領域を確保します。
- Module.HEAPU8.set を使って、JavaScriptの配列データをWasmのメモリにコピーします。
- C言語の関数(_grayscale)を呼び出します。引数としてWasm内のデータポインタと画像のサイズを渡します。関数は結果データが格納されたWasm内のポインタを返します。
- Module.HEAPU8.subarray を使って、Wasm内の結果データをJavaScript側で読み取れる形式(Uint8ClampedArray)で取得します。
- ImageData を作成し putImageData で結果をCanvasに描画します。
- 最後に、_malloc で確保した入力データ用メモリと、C関数内で malloc された結果データ用メモリを _free または用意した _free_memory 関数で 必ず解放します。メモリリークを防ぐために非常に重要です。
実行方法
- grayscale.c をコンパイルして grayscale.js と grayscale.wasm を生成します。
- index_image.html, script_image.js を同じディレクトリに置きます。
- ローカルサーバーを起動し、ブラウザで index_image.html を開きます。
- ファイル選択ボタンで画像ファイルを選ぶと、元画像とグレースケール化された画像が表示されます。
サンプル2: フィボナッチ数列の計算(Rust + wasm-pack)
AutoCADのようなCADソフトウェアや科学技術計算ツールでは、複雑な計算処理にWasmが使われることがあります。ここでは、比較的計算負荷がかかるフィボナッチ数列の計算をRustで実装し、JavaScriptから利用してみましょう。RustとWasmの連携には wasm-pack と wasm-bindgen を使うのが一般的です。
準備するもの:
- Rust開発環境:https://www.rust-lang.org/tools/install
- wasm-pack:cargo install wasm-pack コマンドでインストールします。
ステップ1: Rustプロジェクトの作成
cargo new --lib fibonacci-wasm
cd fibonacci-wasmステップ2:Cargo.toml の編集
Cargo.toml ファイルを開き、以下の依存関係を追加します。
[package]
name = "fibonacci-wasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"] # Wasmとしてビルドするために必要
[dependencies]
wasm-bindgen = "0.2" # RustとJavaScriptの連携を助ける
console_error_panic_hook = { version = "0.1.7", optional = true } # パニック時にコンソールにエラー表示
[features]
default = ["console_error_panic_hook"] # デフォルトでパニックフックを有効にステップ3: Rustコード(src/lib.rs)
// src/lib.rs
use wasm_bindgen::prelude::*;
// パニック時にコンソールに詳細なエラーを出力する設定 (デバッグ用)
#[cfg(feature = "console_error_panic_hook")]
#[wasm_bindgen(start)]
pub fn setup_panic_hook() {
console_error_panic_hook::set_once();
}
// #[wasm_bindgen] を付けるとJavaScriptから呼び出せるようになる
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
if n <= 0 {
return 0;
} else if n == 1 {
return 1;
}
let mut a = 0;
let mut b = 1;
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
// 簡単なベンチマーク用 (再帰実装、大きなnでは遅い)
#[wasm_bindgen]
pub fn fibonacci_recursive(n: u32) -> u64 {
if n <= 0 {
return 0;
} else if n == 1 {
return 1;
} else {
fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
}
}- use wasm_bindgen::prelude::*;:wasm-bindgen を使うためのおまじない。
- #[wasm_bindgen]:この属性を付けた関数や構造体がJavaScriptからアクセス可能になります。
- ここでは効率的なループ実装 fibonacci と、比較のための遅い再帰実装 fibonacci_recursive を用意しました。
ステップ4: Wasmへのビルド
プロジェクトのルートディレクトリ(Cargo.toml がある場所)で以下のコマンドを実行します。
wasm-pack build --target web- --target web:ブラウザ環境向けのWasmとJavaScript連携コード(ES Modules形式)を生成します。
- 成功すると、pkg ディレクトリが作成され、その中に fibonacci_wasm.js(JavaScriptモジュール)、fibonacci_wasm_bg.wasm(Wasm本体)などが生成されます。
ステップ5:HTML(index_fib.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Wasm Fibonacci Example (Rust)</title>
</head>
<body>
<h1>Wasm フィボナッチ数列計算 (Rust)</h1>
<label for="num">n:</label>
<input type="number" id="num" value="30">
<button id="calculateBtn">計算 (ループ)</button>
<button id="calculateRecursiveBtn">計算 (再帰)</button>
<p>結果: <span id="result">?</span></p>
<p>計算時間: <span id="time">?</span> ms</p>
<script type="module" src="script_fib.js"></script>
</body>
</html>ステップ6: JavaScript(script_fib.js)
index_fib.html と同じ階層に script_fib.js を作成し、pkg ディレクトリも同じ階層に置きます。
// script_fib.js
// wasm-packが生成したJSモジュールをインポート
// "./pkg/fibonacci_wasm.js" のパスは環境に合わせて調整してください
import init, { fibonacci, fibonacci_recursive } from './pkg/fibonacci_wasm.js';
const numInput = document.getElementById('num');
const calculateBtn = document.getElementById('calculateBtn');
const calculateRecursiveBtn = document.getElementById('calculateRecursiveBtn');
const resultSpan = document.getElementById('result');
const timeSpan = document.getElementById('time');
async function run() {
// Wasmモジュールの初期化 (非同期)
// init() は wasm-pack が生成した関数で、Wasmファイルの読み込みなどを行う
await init();
console.log('Wasm モジュール初期化完了 (Rust)');
calculateBtn.addEventListener('click', () => {
const n = parseInt(numInput.value, 10);
if (isNaN(n) || n < 0) {
resultSpan.textContent = "不正な入力です";
return;
}
const start = performance.now();
// Rustで実装された関数を直接呼び出す
const result = fibonacci(n);
const end = performance.now();
resultSpan.textContent = result.toString(); // BigIntの可能性もあるためtoString()
timeSpan.textContent = (end - start).toFixed(2);
});
calculateRecursiveBtn.addEventListener('click', () => {
const n = parseInt(numInput.value, 10);
if (isNaN(n) || n < 0) {
resultSpan.textContent = "不正な入力です";
return;
}
// 再帰の方は大きなnで非常に遅くなるので注意
if (n > 40) {
resultSpan.textContent = "n=40 以下にしてください(再帰)";
return;
}
const start = performance.now();
const result = fibonacci_recursive(n);
const end = performance.now();
resultSpan.textContent = result.toString();
timeSpan.textContent = (end - start).toFixed(2);
});
// 初期計算
calculateBtn.click();
}
run();ポイント
- import init, { fibonacci, fibonacci_recursive } from './pkg/fibonacci_wasm.js';:wasm-pack が生成したJavaScriptモジュールをES Modulesとしてインポートします。init 関数と、Rust側で #[wasm_bindgen] を付けた関数がインポートされます。
- await init();:Wasmモジュールを非同期で読み込み、初期化します。これを行わないとRust関数は呼び出せません。
- 初期化後、インポートした fibonacci や fibonacci_recursive を通常のJavaScript関数のように呼び出すことができます。wasm-bindgen が型の変換などを裏側で処理してくれます。
- performance.now() を使って処理時間を計測すると、Wasmによる高速化(特にループ版と再帰版の比較や、同じ処理をJavaScriptで書いた場合との比較)を体感できるかもしれません。
実行方法
- Rustコードをビルドして pkg ディレクトリを生成します。
- index_fib.html, script_fib.js を作成し、pkg ディレクトリと同じ階層に置きます。
- ローカルサーバーを起動し、ブラウザで index_fib.html を開きます。
- 数値を入力してボタンを押すと、Rust(Wasm)で計算されたフィボナッチ数と計算時間が表示されます。
Wasmのユースケース
Wasmは以下のような分野での活用が期待されています。
- 高性能なWebアプリケーション
- ブラウザベースのゲーム(3D描画、物理演算)
- 動画・音声編集、画像処理ツール
- CADソフトウェア、データ可視化ツール
- 科学技術計算シミュレーション
- 既存ソフトウェアのWeb移植
- C/C++などで書かれたデスクトップアプリケーションやライブラリのWeb版
- サーバーレス環境:Node.js以外の言語での実行環境として
まとめ
WebAssemblyは、Webの可能性を大きく広げる技術です。JavaScriptのパフォーマンス限界を超える高速な実行、様々な言語の利用、既存コードの活用など、多くのメリットがあります。
まだ発展途上の技術ではありますが、そのポテンシャルは非常に高く、Google EarthやAutoCAD Web App、Figmaなど、すでに多くのサービスで活用されています。
今回紹介した例は非常にシンプルですが、Wasmの世界への第一歩です。興味を持った方は、ぜひRustとwasm-packを使った開発や、より複雑な処理に挑戦してみてください。WebAssemblyが、あなたのWeb開発の新たな選択肢となるかもしれません。
PR広告

