技術說明 · 2026-08-06

WASM 是什麼,優勢在哪

一句話:它不是「讓 JavaScript 變快」的開關,是「用另一種語言把某一段重寫,而那種語言算數學比較快」。所以成本是重寫,不是設定。這頁講原理、講它適合與不適合什麼、業界怎麼用,以及我們的案例值不值得做。

寫給非工程背景的讀者 對應決定:要不要投一到兩週

先看兩者的執行流程差在哪

JavaScript(你寫的程式 → 瀏覽器邊看邊執行):

① 讀原始碼
瀏覽器拿到的是「文字」
② 邊跑邊猜
執行到 a + b 才知道 a、b 是數字還是文字 —— 每次都要檢查
③ 跑熱了才優化
同一段跑很多次之後,引擎才把它編成機器碼
④ 但優化會被打斷
只要型別變過一次,先前的優化就丟掉重來

WASM(先用 C/Rust 寫好 → 編譯 → 瀏覽器直接跑):

① 編譯時就定案
「這是 32 位元浮點數」寫死在裡面,執行時不用再檢查
② 瀏覽器拿到的已經是接近機器碼
不用猜、不用等它跑熱
③ 從頭到尾一種型別
沒有「優化被打斷」這件事
④ 自己管記憶體
不需要停下來做垃圾回收

真正拉開差距的是「資料怎麼擺」

前面那些都重要,但在「大量重複的數字運算」上,差最多的是這一項 —— 而我們要搬的那段正好是那種。

JavaScript:物件散在記憶體各處

骨頭 1 骨頭 2 骨頭 3 骨頭 4

CPU 要算它們的時候得到處跳著找。每跳一次都可能要去主記憶體撈一趟。

C/WASM:排成一整條連續的陣列

骨頭 1 骨頭 2 骨頭 3 骨頭 4 骨頭 5 骨頭 6

CPU 一次抓一整排(它本來就是這樣設計的)。同樣的運算,撈資料的次數少一個數量級。

用日常的比喻

JavaScript 像用便利貼記帳 —— 每張想寫什麼都行(數字、文字、一整個物件),要加總時得一張一張翻開確認「這張是不是數字」,而且便利貼散在整張桌上。

C/WASM 像用格線表格 —— 每格事先講好只能填數字、而且一格挨著一格。加總時直接一整排掃過去。

所以它的優勢在哪、不在哪

什麼工作搬去 WASM 划不划算為什麼
大量、重複的數字運算
矩陣、內插、物理、影像處理
很划算型別固定、資料連續、進出少 —— 三個優勢全吃到
加解密、壓縮、解碼很划算同上,而且通常一次丟一大包進去、拿一大包出來
一整個引擎搬過去划算幾乎不用跨語言來回(Unity、Figma 就是這樣)
碰畫面元素、碰網頁 DOM不划算WASM 不能直接碰畫面,每次都要繞回 JavaScript ⇒ 光來回就把省的吃光
零散的小函式不划算每次進出都要付轉換成本,而計算本身太短、賺不回來
🔴 最常見的誤解

「裝了 WASM,JavaScript 就會變快」—— 不會。

WASM 不會加速你現有的 JavaScript。它是另外一份用別的語言寫的程式,你要自己決定「把哪一段改用它重寫」。成本是重寫,不是安裝。

業界誰在用

WASM 是 W3C 的正式標準,所有現代瀏覽器都支援。不是實驗性的東西。

Figma
整個編輯器核心(C++)編成 WASM。這是它能在瀏覽器做到桌面級效能的原因
Google Earth 網頁版
C++ 引擎編成 WASM
AutoCAD 網頁版
數十年的 C++ 程式碼搬上瀏覽器
Photoshop 網頁版
Adobe 用 WASM 做的
🔴 Unity / Unreal 的網頁輸出
整個引擎編成 WASM —— 骨骼動畫、物理全部跑在裡面。這個跟我們的案例最像
FFmpeg 網頁版
影音轉檔在瀏覽器裡跑
⚠️ 但有一個差別,會影響我們能不能套用他們的數字

那些案例是「整個引擎本來就用 C++ 寫,然後整包編成 WASM」 —— 幾乎不用跨語言來回。

我們不一樣:這是一個 JavaScript 專案,只想搬一小段過去。那叫混合模式,跨語言的來回成本要自己付。

⇒ 所以他們的「快 2–5 倍」不能直接套在我們身上。

我們的案例:要搬什麼、值多少

我們想搬的是「算每隻怪的骨頭要擺在哪裡」那一段。它在整幀裡的位置:

A
要搬的這段 5.95
畫出來 6.15
其餘 5.35
0 ms20.25 ms

為什麼這一段適合

大量26 隻怪 × 幾十根骨頭
重複每一幀都要重算一遍
純數字取關鍵影格、四元數內插、矩陣相乘 —— 完全不碰畫面、不碰 GPU
進出少丟一批怪的狀態進去,拿一批矩陣出來

四個條件全中 —— 這是少數幾個「搬過去真的會賺」的段落。

能省多少

那段現在要
5.95ms
實測,兩種儀器互相印證
扣掉量測成本後
3.3–4.1ms
這是天花板
再扣跨語言成本
約 3ms
實際可能更少
缺口
7.2–9.6ms
這一項填不滿

成本拆開來看

做什麼估時難在哪
建置工具鏈1–2 天把 Rust/C 編成 WASM,接進現有的打包流程
重寫那段計算3–5 天不是「翻譯」 —— 要重現 three.js 原本的行為,算出來的姿勢必須一模一樣
設計資料怎麼傳2–3 天兩種語言之間傳骨骼資料,而且不能每幀複製一次(複製會把省的吃掉)
正確性驗證2 天不能有一隻怪的手轉錯方向
板子上量、調1–2 天

合計一到兩週。

怪的數量會變,WASM 接得住嗎

接得住,而且這正是標準做法。關鍵在「記憶體怎麼配」—— 有一種做法對、一種做法錯。

做法怎麼運作好不好
每幀重新配置這一幀 20 隻就配 20 隻的空間,下一幀 30 隻就重配錯的做法 配置本身有成本,而且每幀都要付
一次配好上限開場就配一塊「最多 200 隻」的空間,每幀只告訴它「這一幀有幾隻」,它只算前 N 隻標準做法 數量怎麼變都不用重配

我們的程式本來就是這樣寫的 —— 怪的 instance 上限已經寫死是 maxInstances: 200ZombieModelLoader.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 倍」是業界一般值,我們這個案例能加速多少沒有人量過。而這三十輪的經驗是 —— 沿用別人的數字,被咬過好幾次。

誠實區