Belajar WebAssembly - Optimasi Ukuran & Performa
Episode 15 of 23

Belajar WebAssembly - Optimasi Ukuran & Performa

Strategi optimasi modul WebAssembly: mengecilkan ukuran dengan wasm-opt dan wasm-strip, memakai --release dan opt-level z, code splitting dengan lazy instantiation, serta benchmark dan menghindari alokasi di hot-path

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 14 kalian punya strategi testing dan debugging — pada episode ini kita bicara performance engineering: mengecilkan ukuran modul dan mempercepat eksekusinya. Dua metrik ini menentukan pengalaman nyata: ukuran mempengaruhi waktu download, performa mempengaruhi kecepatan interaksi.

Mengapa ini krusial di Wasm? Karena modul Wasm di-download, dikompilasi, dan dijalankan di perangkat pengguna — dan konteks itu berbeda dengan binary server. Di browser, setiap kilobyte diperhitungkan; di serverless, cold start diukur dalam milidetik. Optimasi di sini bukan kemewahan, melainkan syarat kelayakan produksi.

Mengecilkan Ukuran: wasm-opt dan wasm-strip

Binaryen wasm-opt

Binaryen menyediakan wasm-opt — optimizer modul Wasm seperti -O2 untuk compiler:

Install Binaryen
cargo install binaryen
# atau: sudo apt install binaryen

Optimasi modul yang sudah jadi:

Optimasi ukuran dan performa
wasm-opt -O3 -o modul.opt.wasm modul.wasm

Level umum: -O1 (cepat, kecil), -O2 (seimbang), -O3 (maksimal, bisa lebih lambat compile), -Oz (fokus ukuran paling kecil). Untuk workload yang dijalankan sekali (serverless), -O3 dan -Oz hampir selalu menang.

wasm-strip: buang metadata

Modul produksi tidak butuh section name dan DWARF:

Buang metadata
wasm-strip modul.opt.wasm -o modul.stripped.wasm

Hasilnya sering berupa penghematan 10-30% — untuk beberapa modul bahkan lebih besar jika compiler menyertakan banyak debug info. Bandingkan sebelum/sesudah:

Bandingkan ukuran
ls -l modul.wasm modul.opt.wasm modul.stripped.wasm

Optimasi di Sumber: Rust

Optimasi terbaik datang dari sumbernya. Dua pengaturan penting di Cargo.toml:

Cargo.toml
[profile.release]
opt-level = "z"       # ukuran minimal (default "3" untuk kecepatan)
lto = true            # link-time optimization
codegen-units = 1     # satu unit kompilasi -> optimasi global
panic = "abort"       # hilangkan unwinding, ukuran lebih kecil
strip = true          # buang simbol
Build release untuk wasm
cargo build --release --target wasm32-unknown-unknown

opt-level = "z" (size) sering menjadi pilihan untuk Wasm karena waktu download lebih berdampak daripada beberapa persen kecepatan eksekusi. lto dan codegen-units = 1 memungkinkan compiler mengoptimasi melintasi batas crate — biasanya menghemat 5-15% ukuran.

Emscripten: flags ukuran

Di jalur C/C++:

Optimasi ukuran Emscripten
emcc -O3 -flto -s STRIP_DEBUG=1 -s ALLOW_MEMORY_GROWTH=1 hello.c -o hello.js

Code Splitting: Lazy Instantiation

Modul yang besar tidak harus di-download sekaligus. Prinsipnya sama dengan code splitting di frontend: muat hanya saat dibutuhkan.

JSLazy instantiation
let modulBerat = null
 
export async function processBerat(data) {
  if (!modulBerat) {
    const modul = await import("./berat/wasm_berat.js")
    await modul.default()          // init dulu
    modulBerat = modul
  }
  return modulBerat.fungsi(data)
}

Untuk aplikasi dengan banyak fitur — editor, converter, analyzer — lazy loading menjaga bundle awal tetap kecil dan halaman cepat terbuka. Pasangkan dengan caching WebAssembly.Module dari episode 13 untuk meniadakan biaya kompilasi ulang.

Tip

Ukur dulu sebelum mengoptimasi: bundle yang di-download hanyalah sebagian dari total waktu. Jalankan timing kompilasi (WebAssembly.compile) dan eksekusi secara terpisah, lalu putuskan apakah mengecilkan ukuran atau mempercepat eksekusi yang lebih berdampak.

Optimasi Performa Runtime

Hindari Alokasi di Hot-Path

Alokasi memory — baik di Wasm (alloc) maupun di JS (new Uint8Array) — adalah biaya tersembunyi terbesar. Pola yang benar untuk loop panas:

  1. Alokasikan buffer sekali di luar loop.
  2. Gunakan kembali buffer untuk setiap iterasi.
  3. Hindari membuat Vec/String baru per iterasi.
reuse-buffer.rs
// BURUK: alokasi per iterasi
#[wasm_bindgen]
pub fn proses_loop(data: &[f32]) -> Vec<f32> {
    data.iter().map(|x| x * 2.0 + 1.0).collect()
}
 
// BAIK: menulis ke buffer yang sudah disediakan host
#[wasm_bindgen]
pub fn proses_ke_buffer(input: &[f32], out: &mut [f32]) {
    for (i, x) in input.iter().enumerate() {
        out[i] = x * 2.0 + 1.0;
    }
}

Dengan versi kedua, JS menyediakan Uint8Array dari memory yang sama dan mengirim pointer-nya — nol alokasi baru dalam loop.

Minimalkan Crossing the Boundary

Setiap panggilan Wasm ↔ JS punya biaya (marshalling, boxed values). Aturan praktis:

  • Batch operasi kecil menjadi satu panggilan besar.
  • Jangan memanggil fungsi Wasm per-elemen dari loop JS.
  • Transfer data via memory views, bukan lewat objek.

WASI 0.3: I/O Non-Blocking

Untuk I/O server-side, WASI 0.3 async (episode 10) memungkinkan operasi non-blocking tanpa menyita thread — menaikkan throughput jauh dibandingkan pola blokir per request.

Benchmark: Mengukur, Bukan Menebak

Jangan percaya insting — ukur. Tool yang dipakai umum:

Benchmark CLI wasmtime
wasmtime benchmark --profile modul.wasm

Di browser, gunakan Performance API:

JSTiming di browser
const t0 = performance.now()
for (let i = 0; i < 1000; i++) {
  instance.exports.fungsi(1000)
}
const elapsed = performance.now() - t0
console.log("rata-rata per panggilan:", elapsed / 1000, "ms")

Uji pada data yang realistis, setelah warm-up (ingat tier-up JIT di episode 13), dan pada beberapa browser — hasilnya bisa sangat berbeda.

Warning

Optimasi tanpa benchmark adalah spekulasi. Tetapkan baseline dulu, ubah satu variabel, ukur lagi. Perubahan yang terlihat "jelas lebih cepat" sering kali tidak terukur — dan pengukuran yang buruk bahkan bisa menghasilkan keputusan yang merugikan.

Common Pitfalls

  • Optimasi ukuran di semua kondisiopt-level="z" membuat kode sedikit lebih lambat; untuk loop numerik ketat, gunakan opt-level=3 dengan LTO.
  • Lupa wasm-strip — mengirim metadata debug ke production membuang bandwidth.
  • Alokasi tak terkendali — GC di JS dan allocator di Wasm sama-sama mahal di hot-path.
  • Panggilan mikro berulang — puluhan panggilan kecil lebih lambat daripada satu panggilan besar.

Penutup

Pada episode 15 ini, kalian telah menguasai strategi optimasi ukuran dan performa modul WebAssembly.

Inti yang harus dibawa pulang:

  • wasm-opt -O3/-Oz + wasm-strip mengecilkan ukuran modul siap distribusi.
  • opt-level="z", lto, panic="abort" mengecilkan binary Rust; -O3 -flto -s STRIP_DEBUG=1 untuk Emscripten.
  • Code splitting dengan lazy instantiation + caching module meniadakan download & kompilasi berulang.
  • Hindari alokasi di hot-path, batch crossing the boundary, dan selalu benchmark setelah warm-up.

Di episode 16 selanjutnya kita akan membahas keamanan & sandboxing — isolasi memory per-instance, capability-based WASI tanpa raw syscall, Content Security Policy, validasi imports, dan supply-chain security dengan cargo audit. Sampai jumpa di episode 16!

Belajar WebAssembly - Optimasi Ukuran & Performa | Belajar Web Assembly