การ Optimise สำหรับ Production
การเขียน WebAssembly ที่ถูกต้องเป็นแค่ครึ่งหนึ่งของงาน ใน production คุณต้องการให้ binary เล็กที่สุดเท่าที่เป็นไปได้ (download เร็วขึ้น cache อุ่นขึ้น) และเริ่มทำงานได้เร็วที่สุดเท่าที่เป็นไปได้ (time-to-interactive ที่ต่ำลง) บทเรียนนี้รวบรวม playbook ที่สมบูรณ์
ขั้นตอนที่ 1 — คอมไพล์ด้วยการ optimise ขนาดสูงสุด
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 1 — คอมไพล์ด้วยการ optimise ขนาดสูงสุด”ไม่ว่าจะใช้ toolchain ใด ให้เปิดใช้ flag การ optimise ขนาดสูงสุด:
# wasm-opt (standalone หรือ post-compile)wasm-opt -Oz input.wasm -o output.wasm
# Rust ผ่าน wasm-packwasm-pack build --release # wasm-opt -Oz ทำงานโดยอัตโนมัติ
# Emscriptenemcc -Oz source.c -o output.wasm
# TinyGotinygo build -opt=z -target wasm -o output.wasm ./main.goขั้นตอนที่ 2 — ลบ debug sections
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 2 — ลบ debug sections”ข้อมูล debug (DWARF, name sections, producer metadata) อาจเพิ่มขนาด binary ได้ถึงสองเท่า ลบออกสำหรับ production
# ลบ custom sections ทั้งหมดด้วย wasm-optwasm-opt -Oz --strip-debug --strip-producers output.wasm -o output.stripped.wasm
# หรือด้วย wasm-toolswasm-tools strip output.wasm -o output.stripped.wasm
# วัดผลwc -c output.wasm output.stripped.wasmเก็บสำเนาที่ไม่ได้ strip ไว้บน build server เพื่อให้สามารถ symbolicate crash reports ได้
ขั้นตอนที่ 3 — ลด allocator (Rust)
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 3 — ลด allocator (Rust)”สำหรับ Rust targets std allocator เริ่มต้น (dlmalloc ผ่าน wasm-bindgen) เพิ่มประมาณ 10 KB การเปลี่ยนเป็น wee_alloc หรือ lol_alloc ที่ใหม่กว่าสามารถประหยัดได้หลาย kilobytes:
# Cargo.toml[dependencies]wee_alloc = "0.4"
# lib.rs — แทนที่ global allocator# Build (wasm-pack จัดการส่วนที่เหลือ)wasm-pack build --release --target webสำหรับ module ที่ไม่มี dynamic allocation เลย ให้คอมไพล์ด้วย #![no_std] และลบ allocator ออกทั้งหมด
ขั้นตอนที่ 4 — lazy และ streaming instantiation
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 4 — lazy และ streaming instantiation”browser สามารถเริ่มคอมไพล์ไฟล์ .wasm ขณะที่ยังดาวน์โหลดอยู่ถ้าคุณใช้ WebAssembly.instantiateStreaming ซึ่งเร็วกว่าการ fetch ก่อนแล้วค่อยคอมไพล์เสมอ
# Serve ด้วย MIME type ที่ถูกต้องใน JavaScript:
// Streaming (แนะนำ)const { instance } = await WebAssembly.instantiateStreaming( fetch('/module.wasm'), importObject);
// Non-streaming (fallback สำหรับ server ที่ไม่มี MIME type ที่ถูกต้อง)const bytes = await fetch('/module.wasm').then(r => r.arrayBuffer());const { instance } = await WebAssembly.instantiate(bytes, importObject);สำหรับ module ขนาดใหญ่ที่มีการเข้าชมซ้ำ ให้ cache WebAssembly.Module ที่คอมไพล์แล้วใน IndexedDB เพื่อข้ามการคอมไพล์ทั้งหมดในการโหลดครั้งถัดไป
ขั้นตอนที่ 5 — วัดผลทุกอย่าง
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 5 — วัดผลทุกอย่าง”การ optimise โดยไม่มีการวัดผลคือการเดาสุ่ม เก็บ metrics สามอย่างนี้ใน CI pipeline ของคุณ:
# 1. ขนาด binary (raw และ gzip)wc -c output.wasmgzip -c output.wasm | wc -c
# 2. การแจกแจงขนาดตามฟังก์ชันtwiggy top output.wasm | head -20
# 3. Validate binary ที่ optimise แล้วว่าไม่เสียหายwasm-validate output.wasmwasm-tools validate output.wasmการถดถอยใน metrics ทั้งสามอย่างใดอย่างหนึ่งหลังจาก dependency update เป็นสัญญาณให้ตรวจสอบก่อน ship
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
wasm-opt -Oz (การ optimise เชิงรุกสุด) | ลด binary size และเพิ่ม speed ได้มากที่สุดในทุกขั้นตอนของ playbook | build time นานขึ้น และ debug ยากขึ้นเพราะ code ถูกจัดเรียงใหม่จน source เดิมตามไม่ทัน |
เก็บ debug info / --debug-names ไว้ | symbolicate crash report และ debug production ได้ | เพิ่มขนาด binary ที่ผู้ใช้ต้อง download จริง ขัดกับเป้าหมายของบทนี้ |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- Ship
.wasmตรงจาก compiler โดยไม่รันwasm-optเลยสักครั้ง พลาดการลดขนาด/เพิ่มความเร็วที่ได้มาแทบฟรีๆ ในขั้นตอนที่ 1 - กระโดดไปใช้
-Ozหรือ flag เชิงรุกทันทีโดยไม่ profile ก่อนว่าจริงๆ แล้วอะไรใหญ่หรือช้า ทำให้แก้ผิดจุด - Strip debug info ทุก build รวมถึง build ที่ใช้ debug จริง พอมี crash ใน production ก็ไล่ยากเพราะ stack trace ไม่มีชื่อ
💡 ตัวอย่างจากของจริง
Emscripten เรียก
wasm-optของ Binaryen ให้อัตโนมัติเมื่อ build ด้วย-O2/-O3อยู่แล้ว ส่วนทีมที่ ship ไปที่ Cloudflare Workers/CDN มักตั้ง budget ของขนาด binary ไว้ชัดเจน แล้วรัน playbook นี้ทั้งห้าขั้นตอนใน CI ก่อน deploy ทุกครั้ง