WebAssemblyとは
WebAssembly(WASM)は、ブラウザで実行可能なバイナリ命令フォーマットです。C/C++、Rust、Goなどの言語からコンパイルでき、JavaScriptに近いパフォーマンスでネイティブコードを実行できます。
flowchart LR
subgraph Source["ソースコード"]
Rust["Rust"]
Cpp["C/C++"]
Go["Go"]
end
subgraph Compile["コンパイル"]
LLVM["LLVM"]
WASM[".wasm<br/>バイナリ"]
end
subgraph Runtime["実行環境"]
Browser["Browser<br/>Runtime"]
JS["JavaScript<br/>から呼び出し"]
end
Source --> LLVM --> WASM --> Browser
WASM --> JS
特徴:
- ネイティブに近い実行速度
- 言語に依存しない(多言語サポート)
- サンドボックス化された安全な実行
- JavaScriptとの相互運用
- ストリーミングコンパイル対応
パフォーマンス比較
| 処理 | JavaScript | WASM | ネイティブ |
|---|---|---|---|
| 行列乗算 (1000x1000) | 2,500ms | 850ms | 700ms |
| 画像処理 (1920x1080) | 180ms | 72ms | - |
| 暗号化処理 (AES-256) | 320ms | 125ms | - |
※ JSエンジンのJIT最適化により差が縮まるケースもある
Rust + WebAssembly
セットアップ
# wasm-packのインストール
cargo install wasm-pack
# プロジェクト作成
cargo new --lib wasm-example
cd wasm-example
# Cargo.toml
[package]
name = "wasm-example"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console"] }
[profile.release]
opt-level = "z" # サイズ最適化
lto = true # リンク時最適化
Rust実装
// src/lib.rs
use wasm_bindgen::prelude::*;
// JavaScriptから呼び出し可能な関数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 文字列を扱う関数
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// 構造体のエクスポート
#[wasm_bindgen]
pub struct Calculator {
value: f64,
}
#[wasm_bindgen]
impl Calculator {
#[wasm_bindgen(constructor)]
pub fn new() -> Calculator {
Calculator { value: 0.0 }
}
pub fn add(&mut self, x: f64) {
self.value += x;
}
pub fn subtract(&mut self, x: f64) {
self.value -= x;
}
pub fn multiply(&mut self, x: f64) {
self.value *= x;
}
pub fn divide(&mut self, x: f64) -> Result<(), JsValue> {
if x == 0.0 {
return Err(JsValue::from_str("Division by zero"));
}
self.value /= x;
Ok(())
}
pub fn get_value(&self) -> f64 {
self.value
}
pub fn reset(&mut self) {
self.value = 0.0;
}
}
// 配列処理
#[wasm_bindgen]
pub fn sum_array(arr: &[i32]) -> i32 {
arr.iter().sum()
}
// JSのconsoleを使用
#[wasm_bindgen]
pub fn log_to_console(message: &str) {
web_sys::console::log_1(&message.into());
}
// 高速な画像処理の例
#[wasm_bindgen]
pub fn apply_grayscale(data: &mut [u8]) {
for chunk in data.chunks_exact_mut(4) {
let r = chunk[0] as f32;
let g = chunk[1] as f32;
let b = chunk[2] as f32;
// ITU-R BT.601 係数
let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
chunk[0] = gray;
chunk[1] = gray;
chunk[2] = gray;
// chunk[3] (alpha) はそのまま
}
}
// フィボナッチ数列(ベンチマーク用)
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
ビルドと使用
# ビルド
wasm-pack build --target web --release
// JavaScript/TypeScriptからの使用
import init, {
add,
greet,
Calculator,
sum_array,
apply_grayscale,
fibonacci
} from './pkg/wasm_example.js';
async function main() {
// WASMモジュールの初期化
await init();
// 基本的な関数呼び出し
console.log(add(2, 3)); // 5
console.log(greet('World')); // "Hello, World!"
// クラスの使用
const calc = new Calculator();
calc.add(10);
calc.multiply(2);
console.log(calc.get_value()); // 20
// 配列処理
const arr = new Int32Array([1, 2, 3, 4, 5]);
console.log(sum_array(arr)); // 15
// 画像処理
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
apply_grayscale(imageData.data);
ctx.putImageData(imageData, 0, 0);
// パフォーマンス計測
console.time('WASM Fibonacci');
fibonacci(40);
console.timeEnd('WASM Fibonacci');
}
main();
C/C++ + Emscripten
セットアップ
# Emscriptenのインストール
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
C++実装
// src/main.cpp
#include <emscripten/bind.h>
#include <vector>
#include <string>
#include <cmath>
// 基本的な関数
int add(int a, int b) {
return a + b;
}
std::string greet(const std::string& name) {
return "Hello, " + name + "!";
}
// クラスのエクスポート
class Vector2D {
public:
float x, y;
Vector2D() : x(0), y(0) {}
Vector2D(float x, float y) : x(x), y(y) {}
float length() const {
return std::sqrt(x * x + y * y);
}
Vector2D normalize() const {
float len = length();
if (len == 0) return Vector2D();
return Vector2D(x / len, y / len);
}
Vector2D add(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
float dot(const Vector2D& other) const {
return x * other.x + y * other.y;
}
};
// 高速な行列演算
std::vector<float> matrixMultiply(
const std::vector<float>& a,
const std::vector<float>& b,
int n
) {
std::vector<float> result(n * n, 0.0f);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
float sum = 0.0f;
for (int k = 0; k < n; ++k) {
sum += a[i * n + k] * b[k * n + j];
}
result[i * n + j] = sum;
}
}
return result;
}
// Embindでバインディング
EMSCRIPTEN_BINDINGS(module) {
emscripten::function("add", &add);
emscripten::function("greet", &greet);
emscripten::function("matrixMultiply", &matrixMultiply);
emscripten::class_<Vector2D>("Vector2D")
.constructor<>()
.constructor<float, float>()
.property("x", &Vector2D::x)
.property("y", &Vector2D::y)
.function("length", &Vector2D::length)
.function("normalize", &Vector2D::normalize)
.function("add", &Vector2D::add)
.function("dot", &Vector2D::dot);
emscripten::register_vector<float>("FloatVector");
}
ビルド
emcc src/main.cpp \
-o build/module.js \
-s WASM=1 \
-s MODULARIZE=1 \
-s EXPORT_NAME="createModule" \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
--bind \
-O3
JavaScriptとの連携
// WASM モジュールのロード方法
// 1. 基本的なロード
const wasmModule = await WebAssembly.instantiateStreaming(
fetch('/module.wasm'),
importObject
);
// 2. wasm-bindgen使用時
import init, { add } from './pkg/module.js';
await init();
console.log(add(1, 2));
// 3. メモリ共有
const memory = new WebAssembly.Memory({ initial: 256, maximum: 512 });
const importObject = {
env: {
memory,
// JavaScriptの関数をWASMから呼び出し
jsLog: (ptr: number, len: number) => {
const bytes = new Uint8Array(memory.buffer, ptr, len);
const str = new TextDecoder().decode(bytes);
console.log(str);
},
},
};
// 4. 大きなデータの受け渡し
function passLargeArray(wasmInstance: WebAssembly.Instance, data: Float32Array) {
const { memory, alloc, dealloc, process } = wasmInstance.exports as any;
// WASMメモリにデータをコピー
const ptr = alloc(data.byteLength);
const wasmArray = new Float32Array(memory.buffer, ptr, data.length);
wasmArray.set(data);
// 処理実行
process(ptr, data.length);
// 結果を取得
const result = new Float32Array(memory.buffer, ptr, data.length).slice();
// メモリ解放
dealloc(ptr, data.byteLength);
return result;
}
実用的なユースケース
flowchart TB
subgraph UseCases["WebAssemblyの活用事例"]
subgraph Media["1. 画像・動画処理"]
M1["Figma (デザインツール)"]
M2["Squoosh (画像圧縮)"]
M3["FFmpeg.wasm (動画変換)"]
end
subgraph Games["2. ゲーム・3D"]
G1["Unity WebGL"]
G2["Unreal Engine"]
G3["Godot Engine"]
end
subgraph Tools["3. 開発ツール"]
T1["VS Code (Monaco Editor)"]
T2["Pyodide (Python in browser)"]
T3["SQLite WASM"]
end
subgraph Security["4. 暗号・セキュリティ"]
S1["1Password (パスワード管理)"]
S2["Signal (暗号化チャット)"]
S3["ハッシュ計算"]
end
end
サイズ最適化
# Rustの場合
# wasm-opt による最適化
wasm-opt -Oz input.wasm -o output.wasm
# 圧縮
gzip -9 output.wasm
# Brotli圧縮(より効率的)
brotli -9 output.wasm
# Cargo.toml での最適化設定
[profile.release]
opt-level = "z" # サイズ優先の最適化
lto = true # リンク時最適化
codegen-units = 1 # コード生成単位を1に
panic = "abort" # パニック時にアボート
strip = true # シンボル情報を削除
WASI(WebAssembly System Interface)
// WASI対応のRustコード
use std::fs;
use std::io::{self, Write};
fn main() {
// ファイル読み書き
let content = fs::read_to_string("/input.txt").unwrap();
fs::write("/output.txt", content.to_uppercase()).unwrap();
// 標準出力
println!("Hello from WASI!");
// 環境変数
for (key, value) in std::env::vars() {
println!("{}: {}", key, value);
}
}
# WASIターゲットでビルド
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release
# wasmtimeで実行
wasmtime target/wasm32-wasi/release/app.wasm
ブラウザサポート
| ブラウザ | サポート状況 |
|---|---|
| Chrome | 57+ (2017年〜) |
| Firefox | 52+ (2017年〜) |
| Safari | 11+ (2017年〜) |
| Edge | 16+ (2017年〜) |
| Node.js | 8+ (2017年〜) |