ข้ามไปยังเนื้อหา

Binaryen และ wasm-opt

เมื่อ binary .wasm ที่ compile ออกมาจาก Rust, C หรือ AssemblyScript มีขนาดใหญ่เกินไปหรือทำงานช้ากว่าที่ควร นี่คือจุดที่ทีมส่วนใหญ่หันมาใช้ Binaryen เพื่อบีบขนาดและเพิ่ม performance ให้ binary ที่ compile เสร็จแล้ว โดยไม่ต้องแตะ source code เลย

Binaryen คือโครงสร้างพื้นฐาน optimising compiler สำหรับ WebAssembly ของ Google เครื่องมือที่ใช้กันมากที่สุดคือ wasm-opt ซึ่งรับ binary .wasm ที่มีอยู่และเขียนใหม่เพื่อผลิต output ที่เล็กลงหรือเร็วขึ้น คุณไม่จำเป็นต้องเข้าใจ IR ภายในของ Binaryen เพื่อได้รับประโยชน์จากเครื่องมือนี้ — คำสั่งเดียวสามารถลด release binary ได้ 20–40%

wasm-opt ใช้ลำดับของ compiler passes: dead-code elimination, constant folding, function inlining, local CSE และอื่นๆ อีกมาก ทำงานในระดับ binary ทั้งหมด — ไม่จำเป็นต้องมีต้นฉบับหรือ metadata ใดๆ

Terminal window
# ติดตั้งทั่วโลกผ่าน npm
npm install -g binaryen
# หรือติดตั้งผ่าน Homebrew บน macOS
brew install binaryen

wasm-opt ใช้ flags ระดับเดียวกับ Clang/GCC:

Terminal window
# -O1 — การ optimise เบา (build รวดเร็ว)
wasm-opt -O1 input.wasm -o output.wasm
# -O2 — สมดุล (เหมือนกับ release builds ในส่วนใหญ่ของ toolchains)
wasm-opt -O2 input.wasm -o output.wasm
# -O3 — เชิงรุก (เพิ่มความเร็วสูงสุด, code อาจใหญ่ขึ้น)
wasm-opt -O3 input.wasm -o output.wasm
# -Os — optimise เพื่อขนาด (ความเร็วรอง)
wasm-opt -Os input.wasm -o output.wasm
# -Oz — ลดขนาดสูงสุด (การ optimise ช้าที่สุด)
wasm-opt -Oz input.wasm -o output.wasm

สำหรับการ deploy production ส่วนใหญ่ -Oz ให้การแลกเปลี่ยนที่ดีที่สุด: download ที่เล็กลง parse ที่เร็วขึ้น cache ที่อุ่นขึ้น

ในทางปฏิบัติคุณแทบไม่เคยเรียก wasm-opt โดยตรง toolchains ยอดนิยมเหล่านี้เรียกใช้ให้อัตโนมัติในโหมด release:

Toolchainเมื่อ wasm-opt ทำงาน
Emscriptenflags release -O2 / -O3
wasm-pack (Rust)wasm-pack build --release
AssemblyScriptasc --optimize
Go (TinyGo)flag -opt=z

หากคุณใช้ .wasm จากสิ่งเหล่านี้ binary น่าจะถูก optimise แล้ว การเรียก wasm-opt ครั้งที่สองนั้นปลอดภัยแต่ได้ผลลัพธ์ที่ลดน้อยลง

Terminal window
# ลบ DWARF debug section (ลดขนาด สูญเสีย stack traces)
wasm-opt -Oz --strip-dwarf input.wasm -o output.wasm
# ลบ custom sections ทั้งหมด
wasm-opt -Oz --strip-producers input.wasm -o output.wasm
# แสดงสถิติโมดูลก่อนและหลัง
wasm-opt -Oz --print-stats input.wasm -o output.wasm
# เก็บ name section สำหรับ stack traces ที่อ่านได้
wasm-opt -Oz --debuginfo input.wasm -o output.wasm

วัดผลก่อนและหลังเสมอ one-liner bash ง่ายๆ แสดงความแตกต่างของขนาด:

Terminal window
wasm-opt -Oz input.wasm -o output.wasm
echo "Before: $(wc -c < input.wasm) bytes"
echo "After: $(wc -c < output.wasm) bytes"
ตัวเลือกBenefitCost
wasm-opt -Oz (การ optimise เชิงรุกสุด)ได้ทั้ง size และ speed ที่ดีที่สุดbuild time นานขึ้น และ mapping ระหว่าง source กับ output เพี้ยนจน debug ยากขึ้น
เก็บ --debug-names ไว้อ่าน stack trace ได้ ทำให้ debug ง่ายขึ้นมากเพิ่มขนาด binary ที่ต้อง ship ให้ผู้ใช้จริง
  • ข้าม wasm-opt ไปเลยแล้ว ship .wasm ที่ compiler สร้างออกมาตรงๆ พลาดโอกาสลด size/speed ที่ได้มาแทบฟรีๆ
  • เรียก -O3 หรือ -Oz แบบสุ่มสี่สุ่มห้าโดยไม่วัดผลก่อน-หลัง ทำให้ไม่รู้เลยว่า flag ไหนช่วยจริงและช่วยแค่ไหน
  • Strip --debug-names ออกตั้งแต่ dev build ทำให้เวลาต้อง debug crash จริงๆ กลับไม่มี stack trace ที่อ่านออก

💡 ตัวอย่างจากของจริง

Emscripten เรียก wasm-opt ของ Binaryen ให้อัตโนมัติเมื่อ build ด้วย -O2/-O3 ส่วนทีมที่ ship ไปที่ Cloudflare Workers/CDN มักให้ความสำคัญกับ budget ของขนาด binary เป็นพิเศษ จึงนิยมใช้ wasm-opt -Oz กันเป็นมาตรฐาน

wasm-opt flag ไหนลด binary size ได้มากที่สุด?
ต้องมี source code ต้นฉบับเพื่อรัน wasm-opt ไหม?
toolchain ไหนเรียก wasm-opt อัตโนมัติตอน release build?
--strip-dwarf ทำอะไร?