差分ヒートマップ高速化:WASM SIMD + 並列分割 + OffscreenCanvas
Quality Ladder (ツール) や CIパイプライン で大量の差分ヒートマップを生成すると CPU/待ち時間 がボトルネック化します。WASM SIMD と 並列タイル合成で高速化します。
高速化キー
- SIMD: 4画素/8画素同時計算
- タイル: キャッシュ局所性 + 並列
- 共有メモリ: コピー削減
- OffscreenCanvas: メインスレッド解放
1. ボトルネック分析
従来実装 (JS + per-pixel) は 演算/境界チェック/転送 の 3コストが支配。SIMD と タイル分割で分散します。
2. SIMD 実装
// 1. WASM モジュール読み込み (SIMD feature detect)
async function loadWasm(url: string){
if (!('SIMD' in WebAssembly)) console.warn('SIMD 未対応 → fallback');
const resp = await fetch(url);
return WebAssembly.instantiateStreaming(resp, {});
}
// 2. Rust (wasm32) 差分 (RGBA → 平均二乗距離) SIMD 断片
// #[target_feature(enable = "simd128")]
pub unsafe fn diff_rgba(a: v128, b: v128) -> v128 {
// a,b: 4画素 RGBA (u8x16)
let a16 = u16x8_extend_low_u8x16(a); // 下位8要素
let b16 = u16x8_extend_low_u8x16(b);
let d = i16x8_sub(a16 as v128, b16 as v128);
let sq = i16x8_mul(d, d); // (r^2,g^2,...)
sq // 返却: 後段で水平加算
}
3. タイル並列
// 3. タイル並列 Worker 生成
const TILE = 192;
function createTiles(w:number,h:number){
const tiles = [];
for (let y=0;y<h;y+=TILE){
for (let x=0;x<w;x+=TILE){
tiles.push({x,y,w:Math.min(TILE,w-x),h:Math.min(TILE,h-y)});
}
}
return tiles;
}
4. 共有メモリとリング
// 4. 共有メモリリングバッファ (差分結果 → RGBA8)
const pixels = new SharedArrayBuffer(width*height);
// Worker は Atomics.add(index,0) で busy wait 回避 (条件変数が無い場合)
// 完了タイルだけ postMessage({done:true,index})
5. OffscreenCanvas 合成
// 5. OffscreenCanvas 合成 (UI thread 解放)
const worker = new Worker('compositor.js');
worker.postMessage({ canvas: canvasEl.transferControlToOffscreen(), w, h }, [canvasEl.transferControlToOffscreen()]);
6. CI ベンチ
# 6. CI ベンチ (Node + wasm-simd)
node scripts/diff-bench.js --imageA a.png --imageB b.png --wasm build/diff_simd.wasm --json bench.json
node scripts/diff-bench-compare.js bench.json --max-ms 35 --min-speedup 2.5
7. FAQ
- SIMD命令集合? → wasm32 は portable, saturating add/sub を活用
- 差分可視化色? →
dist
を log圧縮し過飽和回避
8. まとめ
WASM SIMD + タイル + OffscreenCanvas でヒートマップ生成は 3〜6x 高速化。Adaptive Q の高速検証にも転用できます。