Skip to main content

wasm-performance

WebAssembly performance: wasm-opt binary optimization, size reduction (panic=abort, LTO, strip), profiling WASM in Chrome DevTools, memory management (linear memory, avoiding GC pressure), SIMD, and multi-threading with SharedArrayBuffer.

Jump to install

Source facts

Repository
aibot88/sec_skill_store
Last source activity
May 27, 2026 at 03:47
Detected SKILL.md language
English
Stars
3
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
wasm-performance
description
WebAssembly performance: wasm-opt binary optimization, size reduction (panic=abort, LTO, strip), profiling WASM in Chrome DevTools, memory management (linear memory, avoiding GC pressure), SIMD, and multi-threading with SharedArrayBuffer.
# WebAssembly Performance Techniques for reducing WASM binary size and maximizing runtime performance. ## When to Activate - Binary is too large for acceptable load time - Profiling reveals WASM execution bottlenecks - Memory usage is growing unexpectedly - Implementing compute-intensive features in WASM - Enabling SIMD or multi-threading for heavy workloads - Reducing JS↔Wasm boundary crossings by batching calls or sharing linear memory directly - Using `twiggy` or `cargo-bloat` to identify which crate or function is dominating binary size --- ## Binary Size Optimization ### Cargo Profile (highest impact) ```toml # Cargo.toml [profile.release] opt-level = "s" # Optimize for size (use "z" for minimal size at cost of speed) lto = true # Link-time optimization — eliminates dead code across crates panic = "abort" # Skip unwinding tables (~50KB savings) codegen-units = 1 # Single codegen unit — better LTO strip = true # Strip debug symbols ``` ### wasm-opt (Binaryen) ```bash # Install cargo install wasm-opt # or: brew install binaryen # Optimize for size after wasm-pack build wasm-opt -Os -o output.wasm input.wasm # Optimize for speed wasm-opt -O3 -o output.wasm input.wasm # Maximum size reduction (slower, aggressive) wasm-opt -Oz --enable-mutable-globals -o output.wasm input.wasm # Enable SIMD optimizations wasm-opt -O3 --enable-simd -o output.wasm input.wasm # wasm-pack integrates wasm-opt automatically in release mode wasm-pack build --release # runs wasm-opt -O automatically ``` ### twiggy — Dependency Size Analyzer ```bash cargo install twiggy # What's taking space? twiggy top target/wasm32-unknown-unknown/release/my_lib.wasm # Full call graph twiggy paths target/wasm32-unknown-unknown/release/my_lib.wasm # Example output: # Shallow Bytes │ Shallow % │ Item # ───────────────┼───────────┼──────────────────── # 81996 ┊ 36.10% ┊ "function names" subsection # 36713 ┊ 16.16% ┊ fmt::Display for f64 # 12300 ┊ 5.41% ┊ serde::de::Visitor ``` ### cargo-bloat — Rust Bloat Detector ```bash cargo install cargo-bloat # Find largest functions cargo bloat --release --target wasm32-unknown-unknown -n 20 # Find largest crates cargo bloat --release --target wasm32-unknown-unknown --crates ``` ### Reducing Dependency Footprint ```toml # Disable default features you don't need [dependencies] serde = { version = "1", default-features = false, features = ["derive"] } chrono = { version = "0.4", default-features = false, features = ["serde"] } # Use lighter alternatives for WASM # Instead of std HashMap (larger): use ahash or a no-std map ``` --- ## Runtime Performance ### Avoid JS↔Wasm Boundary Crossings Every call across the JS/Wasm boundary has overhead. Batch work: ```rust // BAD: many small calls across the boundary #[wasm_bindgen] pub fn process_item(item: u32) -> u32 { item * 2 + 1 } // In JS: // for (const item of items) result.push(process_item(item)); // N crossings // GOOD: single call with slice #[wasm_bindgen] pub fn process_batch(items: &[u32]) -> Vec<u32> { items.iter().map(|&x| x * 2 + 1).collect() } // In JS: // const result = process_batch(new Uint32Array(items)); // 1 crossing ``` ### Share Memory Instead of Copying ```rust use wasm_bindgen::prelude::*; // Allocate memory in WASM heap #[wasm_bindgen] pub fn alloc(size: usize) -> *mut u8 { let mut buf: Vec<u8> = Vec::with_capacity(size); let ptr = buf.as_mut_ptr(); std::mem::forget(buf); // Give ownership to caller ptr } #[wasm_bindgen] pub fn dealloc(ptr: *mut u8, size: usize) { unsafe { drop(Vec::from_raw_parts(ptr, 0, size)) } } // JS: write directly into WASM memory #[wasm_bindgen] pub fn process_buffer(ptr: *const u8, len: usize) -> u32 { let slice = unsafe { std::slice::from_raw_parts(ptr, len) }; slice.iter().map(|&b| b as u32).sum() } ``` ```typescript // Direct memory access from JS const memory = wasm_instance.memory as WebAssembly.Memory; const ptr = alloc(data.length); new Uint8Array(memory.buffer, ptr, data.length).set(data); // Zero copy const result = process_buffer(ptr, data.length); dealloc(ptr, data.length); ``` ### SIMD (WebAssembly SIMD) ```rust // Enable SIMD via target features // RUSTFLAGS="-C target-feature=+simd128" use std::arch::wasm32::*; #[cfg(target_arch = "wasm32")] pub fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 { assert_eq!(a.len(), b.len()); let mut sum = f32x4_splat(0.0); let chunks = a.len() / 4; for i in 0..chunks { let a_vec = unsafe { v128_load(a.as_ptr().add(i * 4) as *const v128) }; let b_vec = unsafe { v128_load(b.as_ptr().add(i * 4) as *const v128) }; sum = f32x4_add(sum, f32x4_mul(a_vec, b_vec)); } let arr: [f32; 4] = unsafe { std::mem::transmute(sum) }; arr.iter().sum() } ``` ```bash # Build with SIMD RUSTFLAGS="-C target-feature=+simd128" wasm-pack build --release # wasm-opt with SIMD wasm-opt -O3 --enable-simd output.wasm -o output_simd.wasm ``` ### Multi-Threading with SharedArrayBuffer ```rust // Cargo.toml [dependencies] wasm-bindgen-rayon = "1.0" // lib.rs use wasm_bindgen_rayon::init_thread_pool; #[wasm_bindgen] pub async fn setup_threads() { init_thread_pool(4).await; // 4 web workers } #[wasm_bindgen] pub fn parallel_sum(data: &[f64]) -> f64 { use rayon::prelude::*; data.par_iter().sum() } ``` ```html <!-- Required headers for SharedArrayBuffer --> <meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin"> <meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp"> ``` --- ## Memory Management ### Linear Memory Growth ```rust // Check current memory usage #[wasm_bindgen] pub fn memory_usage() -> usize { // Returns pages × 64KB core::arch::wasm32::memory_size(0) * 64 * 1024 } // Grow memory explicitly (rarely needed — usually automatic) #[wasm_bindgen] pub fn ensure_capacity(bytes: usize) { let current = core::arch::wasm32::memory_size(0) * 65536; if bytes > current { let pages_needed = (bytes - current + 65535) / 65536; core::arch::wasm32::memory_grow(0, pages_needed); } } ``` ### Avoid Memory Leaks ```rust // WRONG: forgot to drop → memory leak #[wasm_bindgen] pub fn leaky(size: usize) -> *mut u8 { let mut v: Vec<u8> = vec![0; size]; let ptr = v.as_mut_ptr(); std::mem::forget(v); // Intentional — but caller must free ptr } // CORRECT: use Box for single values, provide dealloc #[wasm_bindgen] pub fn create_buffer(size: usize) -> *mut u8 { Box::into_raw(vec![0u8; size].into_boxed_slice()) as *mut u8 } #[wasm_bindgen] pub unsafe fn free_buffer(ptr: *mut u8, size: usize) { let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr, size)); } ``` --- ## Profiling ### Chrome DevTools ```javascript // Enable DWARF debug info for source maps // wasm-pack build --dev (dev build with debug info) // Profile in DevTools: // 1. Open DevTools → Performance tab // 2. Enable: Settings → "WebAssembly debugging: Enable DWARF support" // 3. Record a profile // 4. In flame chart: Wasm functions appear with Rust source names ``` ### console.time in Rust ```rust use web_sys::console; #[wasm_bindgen] pub fn benchmark_process(data: &[u8]) -> u32 { console::time_with_label("process"); let result = expensive_computation(data); console::time_end_with_label("process"); result } ``` ### Counting Operations ```rust use std::sync::atomic::{AtomicU64, Ordering}; static OPS_COUNT: AtomicU64 = AtomicU64::new(0); pub fn hot_function(x: u64) -> u64 { OPS_COUNT.fetch_add(1, Ordering::Relaxed); x * x + x + 1 } #[wasm_bindgen] pub fn get_ops_count() -> u64 { OPS_COUNT.load(Ordering::Relaxed) } ``` --- ## Size Benchmarks | Technique | Savings | |-----------|---------| | `opt-level = "s"` | 20–40% | | `lto = true` | 10–30% | | `panic = "abort"` | ~50KB | | `wasm-opt -Os` | 10–20% | | `strip = true` | 20–60% | | Remove unused features | varies | **Typical final size for a medium-complexity library:** - Before optimization: 500KB–2MB - After all techniques: 50KB–200KB --- ## Loading Strategy by Size | Binary Size | Strategy | |-------------|---------| | < 100KB | Inline as base64 or bundle | | 100KB–1MB | Streaming instantiation (`WebAssembly.instantiateStreaming`) | | > 1MB | Lazy load on demand + loading indicator | ```typescript // Streaming instantiation (most efficient) const { instance } = await WebAssembly.instantiateStreaming( fetch('/assets/my_lib.wasm'), importObject ); // Fallback for servers without correct Content-Type async function loadWasm(url: string) { try { return await WebAssembly.instantiateStreaming(fetch(url)); } catch { const bytes = await (await fetch(url)).arrayBuffer(); return WebAssembly.instantiate(bytes); } } ``` ## Reference - `wasm-patterns` — wasm-bindgen, WASI, Component Model, JS integration - `rust-patterns` — Rust idioms that affect WASM output - wasm-opt docs: <https://github.com/WebAssembly/binaryen> - Rust WASM book: <https://rustwasm.github.io/book/>
View on GitHub