一句話:它不是「讓 JavaScript 變快」的開關,是「用另一種語言把某一段重寫,而那種語言算數學比較快」。所以成本是重寫,不是設定。這頁講原理、講它適合與不適合什麼、業界怎麼用,以及我們的案例值不值得做。
JavaScript(你寫的程式 → 瀏覽器邊看邊執行):
WASM(先用 C/Rust 寫好 → 編譯 → 瀏覽器直接跑):
前面那些都重要,但在「大量重複的數字運算」上,差最多的是這一項 —— 而我們要搬的那段正好是那種。
CPU 要算它們的時候得到處跳著找。每跳一次都可能要去主記憶體撈一趟。
CPU 一次抓一整排(它本來就是這樣設計的)。同樣的運算,撈資料的次數少一個數量級。
JavaScript 像用便利貼記帳 —— 每張想寫什麼都行(數字、文字、一整個物件),要加總時得一張一張翻開確認「這張是不是數字」,而且便利貼散在整張桌上。
C/WASM 像用格線表格 —— 每格事先講好只能填數字、而且一格挨著一格。加總時直接一整排掃過去。
| 什麼工作 | 搬去 WASM 划不划算 | 為什麼 |
|---|---|---|
| 大量、重複的數字運算 矩陣、內插、物理、影像處理 | 很划算 | 型別固定、資料連續、進出少 —— 三個優勢全吃到 |
| 加解密、壓縮、解碼 | 很划算 | 同上,而且通常一次丟一大包進去、拿一大包出來 |
| 一整個引擎搬過去 | 划算 | 幾乎不用跨語言來回(Unity、Figma 就是這樣) |
| 碰畫面元素、碰網頁 DOM | 不划算 | WASM 不能直接碰畫面,每次都要繞回 JavaScript ⇒ 光來回就把省的吃光 |
| 零散的小函式 | 不划算 | 每次進出都要付轉換成本,而計算本身太短、賺不回來 |
「裝了 WASM,JavaScript 就會變快」—— 不會。
WASM 不會加速你現有的 JavaScript。它是另外一份用別的語言寫的程式,你要自己決定「把哪一段改用它重寫」。成本是重寫,不是安裝。
WASM 是 W3C 的正式標準,所有現代瀏覽器都支援。不是實驗性的東西。
那些案例是「整個引擎本來就用 C++ 寫,然後整包編成 WASM」 —— 幾乎不用跨語言來回。
我們不一樣:這是一個 JavaScript 專案,只想搬一小段過去。那叫混合模式,跨語言的來回成本要自己付。
⇒ 所以他們的「快 2–5 倍」不能直接套在我們身上。
我們想搬的是「算每隻怪的骨頭要擺在哪裡」那一段。它在整幀裡的位置:
| 大量 | 26 隻怪 × 幾十根骨頭 |
| 重複 | 每一幀都要重算一遍 |
| 純數字 | 取關鍵影格、四元數內插、矩陣相乘 —— 完全不碰畫面、不碰 GPU |
| 進出少 | 丟一批怪的狀態進去,拿一批矩陣出來 |
四個條件全中 —— 這是少數幾個「搬過去真的會賺」的段落。
| 做什麼 | 估時 | 難在哪 |
|---|---|---|
| 建置工具鏈 | 1–2 天 | 把 Rust/C 編成 WASM,接進現有的打包流程 |
| 重寫那段計算 | 3–5 天 | 不是「翻譯」 —— 要重現 three.js 原本的行為,算出來的姿勢必須一模一樣 |
| 設計資料怎麼傳 | 2–3 天 | 兩種語言之間傳骨骼資料,而且不能每幀複製一次(複製會把省的吃掉) |
| 正確性驗證 | 2 天 | 不能有一隻怪的手轉錯方向 |
| 板子上量、調 | 1–2 天 |
合計一到兩週。
接得住,而且這正是標準做法。關鍵在「記憶體怎麼配」—— 有一種做法對、一種做法錯。
| 做法 | 怎麼運作 | 好不好 |
|---|---|---|
| 每幀重新配置 | 這一幀 20 隻就配 20 隻的空間,下一幀 30 隻就重配 | 錯的做法 配置本身有成本,而且每幀都要付 |
| 一次配好上限 | 開場就配一塊「最多 200 隻」的空間,每幀只告訴它「這一幀有幾隻」,它只算前 N 隻 | 標準做法 數量怎麼變都不用重配 |
我們的程式本來就是這樣寫的 —— 怪的 instance 上限已經寫死是 maxInstances: 200(ZombieModelLoader.ts 三處),而實戰場上是 26–38 隻。連新的上限都不用訂。
WASM 的記憶體是一塊連續的空間,而 JavaScript 可以直接看進去。所以理想的流程是:
① JavaScript 把 26 隻怪的狀態直接寫進那塊共用空間
② 呼叫 WASM:「算前 26 隻」
③ WASM 算完,結果也寫在同一塊空間
④ JavaScript 直接讀出來送 GPU
⇒ 全程零複製。這很重要 —— 前面說過「跨語言的來回成本會把省的吃掉」,而這個設計把那個成本降到最低。
① 記憶體是照上限配的。200 隻 × 幾十根骨頭 × 一個矩陣 —— 大概幾 MB,很小。但它從頭到尾都佔著,不會因為場上只有 26 隻就縮回去。
② 超過上限要重配。WASM 的記憶體可以長大,但那一幀會頓一下。上限 200、實戰 26–38,不會碰到 —— 但如果之後玩法改成要出一百多隻,這件事要重新看。
搬一小段(例如只搬四元數內插),在板子上量出「我們這個案例,WASM 比 JavaScript 快幾倍」。
成本:約一天。產出:一個數字。
倍率只有 1.2 倍 ⇒ 那兩週不該花。
倍率有 3 倍 ⇒ 5.95ms 能砍掉約 4ms,值得。
理由很簡單:「快 2–5 倍」是業界一般值,我們這個案例能加速多少沒有人量過。而這三十輪的經驗是 —— 沿用別人的數字,被咬過好幾次。