Performance Profiling Wasm — WebAssembly

Performance Profiling WebAssembly Wasm memang cepat, tapi "berapa cepat?" dan "bisa lebih cepat?" memerlukan pengukuran yang akurat. Profiling membantu…

Profiling artinya mengukur ke mana waktu dan byte program kamu pergi, sebelum mengubah apa pun. Bayangin Bu Sri ingin tahu kenapa sarapan di Homestay Kabut sering telat. Dia gak langsung beli kompor baru. Dia memegang stopwatch dan mencatat total waktu tiap pagi, lalu satu pagi dia mengikuti juru masak dan mencatat berapa lama tiap pekerjaan, lalu membuka gudang untuk melihat apa yang paling makan tempat. Toples tanpa label cuma bisa dicatat sebagai "toples nomor 0". Stopwatch itu benchmark, mengikuti juru masak itu profiler, membuka gudang itu analisis ukuran binary, dan label toplesnya adalah name section di file .wasm.

🧪 Tebakan cepat: olahfoto.wasm di-build ulang dengan opt-level = "z" (fokus ke ukuran) sebagai ganti opt-level = 3. File-nya turun dari 4.177 jadi 2.637 byte. Blur σ 2 di foto 1200 × 800 jadi lebih cepat, sama saja, atau lebih lambat? Tebak dulu, jawabannya ada di bagian "Ukuran lawan kecepatan".

Ukur dulu, dengan jujur

Aturannya sudah kamu kenal dari pelajaran "Wasm vs JavaScript": performance.now(), banyak putaran, buang putaran pertama (pemanasan), ambil median, dan cek hasilnya sama.

function ukur(fn, putaran = 10, pemanasan = true) {
  const waktu = [];
  for (let i = 0; i < putaran; i++) {
    const mulai = performance.now();
    fn();
    waktu.push(performance.now() - mulai);
  }
  const dipakai = (pemanasan ? waktu.slice(1) : waktu).sort((a, b) => a - b);
  return { pertama: waktu[0], median: dipakai[Math.floor(dipakai.length / 2)] };
}

Diukur di Chromium 147, MacBook M1 Pro yang sedang sibuk, median setelah pemanasan (11 putaran, build debug 4 putaran):

Pekerjaan di foto 1200 × 800 JavaScript Wasm (release) Wasm (build debug)
Grayscale 1,98 ms 0,90 ms 8,6 ms
Blur Gaussian σ 2 155 ms 49 ms 646 ms

Build debug (cargo build tanpa --release, opt-level = 0) 13 kali lebih lambat dari release, dan 4 kali lebih lambat dari JavaScript. Jangan pernah menyimpulkan "Wasm lambat" dari build debug.

Chrome DevTools: Performance dan nama fungsi

Benchmark bilang berapa lama. Profiler bilang di mana. Caranya: buka DevTools (F12), tab Performance, klik Record, jalankan blur-nya, klik Stop. Di tab Bottom-Up, urutkan berdasarkan Self Time: fungsi yang paling lama bekerja ada di atas. Fungsi Wasm muncul di sana bersama fungsi JavaScript.

Nama yang muncul tergantung isi file .wasm-nya. Ini hasil profiler yang sama dengan panel Performance (Chromium 147, 3 kali grayscale dan blur):

Build olahfoto.wasm Baris teratas di profiler Self time
strip = true (4.177 byte) wasm-function[0] di wasm://wasm/8de44fbe 177,6 ms
bawaan Cargo, name section ikut (4.761 byte) _RNvCsaWdBoUbM9cW_9olah_foto14blur_satu_arah di wasm://wasm/olah_foto.wasm-c7e9edd6 177,4 ms

Untuk debugging sampai ke baris kode sumber, butuh DWARF (info debug lengkap): debug = true di Cargo (olahfoto jadi 38.183 byte) atau -g di Emscripten, ditambah ekstensi Chrome C/C++ DevTools Support (DWARF). Dengan itu kamu bisa pasang breakpoint di baris C/C++, melihat isi variabel, dan nama fungsi di Call Stack. Kirim build tanpa info debug ke pengguna, simpan build dengan nama untuk profiling.

Apa yang besar di binary-ku

wasm-tools objdump (wasm-tools 1.261.0) memecah file per section, dalam byte:

$ wasm-tools objdump olahfoto.wasm
  types                    |        0xa -       0x26 |        28 bytes | 4 count
  functions                |       0x28 -       0x34 |        12 bytes | 11 count
  memories                 |       0x36 -       0x39 |         3 bytes | 1 count
  exports                  |       0x3b -       0xa5 |       106 bytes | 8 count
  code                     |       0xa8 -     0x1051 |      4009 bytes | 11 count
  custom "name"            |     0x1059 -     0x11aa |       337 bytes | 1 count
  custom "producers"       |     0x11b6 -     0x11f9 |        67 bytes | 1 count
  custom "target_features" |     0x120c -     0x1299 |       141 bytes | 1 count

Lalu wasm-objdump -x -j Code olahfoto.wasm (wabt 1.0.42) memberi ukuran tiap fungsi, dengan nama dari name section: resize_bilinear 930 byte, grayscale_simd 861, resize_area 815, blur_satu_arah 757, resize_nearest 291, grayscale 272.

Di modul yang lebih besar, yang makan tempat sering bukan kodemu. Versi komponen olahfoto di pelajaran berikutnya memakai Vec, String, dan format! dari std. Modul intinya 21.003 byte, dan 5.204 byte di antaranya cuma satu fungsi: dlmalloc::malloc, allocator bawaan std. Mesin format Rust (core::fmt) juga ikut, 17 fungsi dengan total 4.345 byte, karena format! dan pesan panic. Tool khusus untuk ini, twiggy (cargo install twiggy, lalu twiggy top dan twiggy dominators), masih bisa dipasang (versi 0.8.0), tapi repo-nya diarsipkan sejak 14 Maret 2026. wasm-objdump dan wasm-tools masih dirawat.

Ukuran lawan kecepatan: opt-level, LTO, wasm-opt

[profile.release]
opt-level = 3       # 3 = paling cepat; "s" dan "z" = fokus ukuran
lto = true          # optimasi lintas crate saat linking
codegen-units = 1   # satu unit: optimasi lebih menyeluruh, compile lebih lama
panic = "abort"     # tanpa mesin unwind
strip = true        # buang name section dan info debug

Setelah itu, wasm-opt (dari Binaryen, versi 133) mengoptimasi file .wasm-nya sendiri. -O1 sampai -O4 fokus ke kecepatan (makin tinggi makin lama prosesnya), -Os dan -Oz fokus ke ukuran:

wasm-opt -O3 --enable-simd --enable-bulk-memory --enable-sign-ext \
  --enable-nontrapping-float-to-int --enable-mutable-globals \
  --enable-multivalue --enable-reference-types \
  olahfoto.wasm -o olahfoto.opt.wasm

Semua build ini dari kode Rust yang sama, hasilnya sama byte demi byte, diukur bergantian (median, Chromium 147 dan WebKit 26.4):

Build Ukuran Blur σ 2 Chromium WebKit
opt-level = 3, LTO, codegen-units = 1, strip 4.177 byte 49 ms 50 ms
yang sama + wasm-opt -O3 3.683 byte 49 ms 49 ms
opt-level = "s" 2.504 byte 95 ms 173 ms
opt-level = "z" 2.637 byte 131 ms 152 ms
"z" + wasm-opt -Oz 2.284 byte 126 ms
tanpa LTO, codegen-units = 16 7.453 byte 47 ms 48 ms
build debug + wasm-opt -O3 15.548 byte 711 ms

Jadi jawaban tebakannya: lebih lambat, 49 ms jadi 131 ms, hampir 3 kali. Profiler menunjukkan sebabnya: di build "z", fungsi Ord::clamp muncul sendiri dengan self time 37 ms. Demi hemat byte, "z" gak menyisipkan (inline) fungsi kecil itu dan mematikan vektorisasi loop, jadi tiap titik blur memanggil fungsi. Grayscale tetap 0,9 ms di semua build release.

💡 Kapan JavaScript sudah cukup

⚠️ Jebakan yang sering ketemu

TL;DR: Ukur dulu sebelum mengoptimasi: performance.now(), banyak putaran, buang pemanasan, median, hasil dicek sama, dan selalu build release (build debug: blur 646 ms lawan 49 ms). Tab Performance di Chrome DevTools menampilkan fungsi Wasm dengan nama aslinya kalau file-nya punya name section atau DWARF; tanpa itu cuma wasm-function[0]. DWARF plus ekstensi "C/C++ DevTools Support (DWARF)" memberi breakpoint di kode sumber. wasm-tools objdump dan wasm-objdump -x menunjukkan section dan fungsi yang paling besar; twiggy masih bisa dipasang tapi diarsipkan. Untuk mengecilkan: release, wasm-opt, strip (4.761 jadi 3.683 byte tanpa kehilangan kecepatan). opt-level = "z" memang lebih kecil (2.637 byte) tapi blur jadi 131 ms karena fungsi kecil gak di-inline. Kalau JavaScript sudah di bawah anggaran (16,7 ms per frame, 50 ms per tugas), Wasm belum perlu.

Yang akan kamu pelajari