WASM 在桌機上表現很好。Chrome、Firefox、Edge 都能把我們的像素處理程式編譯成接近原生的機器碼。
所以當在我的 MacBook 上看見「60 FPS 灰階即時預覽」,你會自然以為:「OK,這套上線沒問題。」
但換成手機,整個畫面就開始亂掉。
桌機的 CPU 通常是高時脈、寬記憶體頻寬、多核心;
而手機的 CPU 雖然也號稱「八核」,但多數核心是低功耗、低頻的效能核心(efficiency core)。
更關鍵的是:記憶體架構不同。
在桌機上,WASM 存取 memory.buffer
幾乎等於直接碰 DRAM;
在手機上,尤其是 WebView 或 Safari iOS,WASM 的線性記憶體會被封裝在一層 JIT sandbox 裡,
有額外的 boundary check、cache miss penalty。
這導致我們的零拷貝資料流在桌機上完美、
但在手機上,每個像素的讀寫都變成昂貴的跨層操作,然後你的手機就會像今天的烤肉一樣燒起來。
Day 16 我們設計的零 copy 資料流,是以「一次進、一次出」為目標。
但這假設在行動裝置上並不完全成立。
當 ImageData.data
的底層 ArrayBuffer
和 WASM 的 memory.buffer
都變大時:
memory.grow
更貴:在手機上 WebAssembly.Memory.grow()
可能要複製整個 buffer;所以當你丟一張 4000×3000 的圖(約 48 MB RGBA)進去,
桌機會笑笑地跑完,
手機可能直接 reload。
還有就是 Day 14 我們用了多工 WorkerPool,理論上可以分散 CPU。
但在手機上,每開一個 Worker 就多一份記憶體映射。
同樣的 48 MB 圖片 × 3 Worker = 144 MB。
如果你的手機是低 RAM(4 GB 以下),
這光是 WebView 進程就夠讓系統判定「記憶體壓力過高」,強制回收。
因此,在手機上要改成:
WebGL
/ WebGPU
);桌機跑得動,只代表:
演算法沒 bug,且在理想環境中可運行。
但手機要的,是:
演算法能在資源受限、記憶體受監控、JIT 被封印的情況下,仍然「不死」。
要做到這件事,我們需要重新思考:
我們可以把這些環境差異「包成診斷」。
當偵測到 runtime 是行動裝置時:
if (/Mobi|Android/i.test(navigator.userAgent)) {
warn("Detected mobile device — performance may vary.")
}
甚至可以在 WASM 端提供一個「環境健康檢查」:
#[wasm_bindgen]
pub fn env_report() -> JsValue {
let ua = web_sys::window().unwrap().navigator().user_agent().unwrap_or_default();
let heap = js_sys::WebAssembly::Memory::new(&js_sys::Object::new()).maximum().unwrap_or(0);
js_sys::Object::from_entries(&js_sys::Array::of2(
&JsValue::from("userAgent"), &JsValue::from(ua),
));
}
前端拿到後,就能在 debug 面板印出目前的環境特徵。
今天中秋節烤肉外加月老生日,希望大家可以好好烤肉還有幸福 (σ′▽‵)′▽‵)σ