Skip to main content

perf

Speed and memory performance rules for Rust crates, the Node addon, webui-framework, and webui-router.

Informations de source

Dépôt
microsoft/webui
Dernière activité de la source
26 août 2026 à 04:54
Langue détectée de SKILL.md
anglais
Étoiles
98
Forks
24

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
perf
description
Speed and memory performance rules for Rust crates, the Node addon, webui-framework, and webui-router.
# Performance WebUI's value proposition is speed and low memory usage. Every change to core Rust crates, `@microsoft/webui-framework`, or `@microsoft/webui-router` must be evaluated through two lenses: **throughput** (how fast) and **memory** (how little). Server memory is not cheap. Client memory is not unlimited. Every allocation that can be avoided is a win on both sides. Use this skill when modifying any performance-sensitive code across the stack. ## Rust - speed rules These apply to `webui-handler`, `webui-state`, `webui-expressions`, `webui-parser`, `webui-protocol`, `webui-ffi`, and `webui-node`. 1. **No `format!()` in writer output.** Use sequential `writer.write()` calls. `format!` allocates a temporary `String` every invocation. 2. **No `.to_string()` on `Cow`.** Write `Cow<str>` directly to avoid defeating zero-copy. 3. **No `collect::<Vec<_>>()` on splits.** Iterate `path.split('.')` directly. Collecting allocates a `Vec` for sequential access. 4. **No redundant scans.** Use `first_part.len() == path.len()` instead of `path.contains('.')`. 5. **No `String::from(ch)` in escape loops.** Use `ch.encode_utf8(&mut buf)` with a `[u8; 4]` stack buffer, or batch contiguous safe chars into a single write. 6. **No `format!` in hex parsing.** Use direct arithmetic (`(hi_nibble << 4) | lo_nibble`) instead of `u8::from_str_radix(&format!(...))`. 7. **No per-request template re-parsing.** Pre-parse at protocol load time and reuse. 8. **No silent `unwrap_or` defaults.** If `binding_stack.pop()` returns `None`, that's a protocol error - propagate it, don't mask it. 9. **Keep cold error/diagnostic construction off the hot path.** Mark error builders `#[cold]`/`#[inline(never)]` so they don't inline into hot functions and perturb their code layout. Adding cold error code with **no** hot-path work has regressed parse benches ~4-5% purely via layout - keep per-element fast-paths inlinable and push the cold fallback out-of-line. See `skills/diagnostics/SKILL.md` §7. ## Rust - memory rules 1. **No cloning large state trees.** Use `evaluate_with_resolver` with a closure. Cloning duplicates the entire JSON tree in memory. 2. **No cloning HashMaps for scope.** Save/restore only the overwritten key on loop iteration. A HashMap clone copies every entry. 3. **No cloning `String` values for read-only access.** Use `s.as_str()` for `Value::String` branches; only create owned strings for `Number`/`Bool` via a scratch buffer or `Cow`. 4. **Pre-allocate buffers.** Use `Vec::with_capacity` / `String::with_capacity` when size is known or estimable. For HTML output, 4096 bytes is a reasonable starting point. 5. **Prefer `&str` and slices over owned types.** Pass by reference when the callee only reads. Move clone decisions to the caller. 6. **Use `Cow<'_, str>` when a value is sometimes borrowed, sometimes owned.** Avoids unconditional allocation. 7. **No deep-cloning protocol or state per request.** Use `Arc<T>` with clone-on-write or snapshot swapping. 8. **Cap memory for untrusted inputs.** File reads during discovery must have size limits. A 100MB HTML file should not cause OOM. ## TypeScript - `@microsoft/webui-framework` rules These apply to `packages/webui-framework` (the client-side Web Component runtime). ### Speed 1. **Single-pass hydration.** The framework walks the DOM once to connect all bindings. No multi-pass scanning. 2. **Path-indexed targeted updates.** When an `@observable` changes, only bindings referencing that property are visited - not the entire template. 3. **DOM cloning over innerHTML.** Use `cloneNode(true)` from cached template fragments. Never use `innerHTML` for component creation. 4. **Direct event listeners.** Attach `@event` bindings to the bound element, never a shared render root. `$wireEvents` runs once per block instance, so delegating stacks one listener per block on the same node and fires all of them per dispatch — O(N) for no reduction in listener count. 5. **Microtask coalescing.** Multiple property changes within the same synchronous block batch into a single DOM update via `queueMicrotask`. 6. **Cursor-based repeat reconciliation.** `<for>` block updates use a diff algorithm that only calls `insertBefore` on nodes that actually moved. Append/prepend/remove are O(1). 7. **No `for..in` on objects.** Use `Object.keys()` with an indexed `for` loop — faster and prototype-safe without needing `Object.hasOwn`. Applies to `setState`, `setInitialState`, and any code iterating user-provided objects. ### Memory 1. **No framework in the GC.** Minimize object allocations during reactive updates. Reuse binding objects, don't recreate them. 2. **Template cache is `WeakMap`-keyed.** Parsed template DOMs are cached per metadata object. When metadata is released (e.g., via `Router.gc()`), the cache entry becomes GC-eligible. 3. **No per-update array allocations.** Avoid `.filter()`, `.map()`, `.slice()` in the update hot path. Use index-based iteration. 4. **Strip SSR markers after hydration.** Comment nodes used as markers are removed from the DOM once wiring is complete - they don't persist as memory overhead. 5. **Scope frames are stack-allocated.** `<for>` loop item variables use a linked-list scope chain, not cloned Maps or Objects. ## TypeScript - `@microsoft/webui-router` rules These apply to `packages/webui-router` (the client-side SPA router). ### Speed 1. **Server does route matching.** The client does not re-match routes. The server returns the matched `chain` array; the client diffs old vs new and mounts only changed components. 2. **Lazy loading via dynamic import.** Route component JS is fetched only on first navigation to that route. 3. **Chain diffing, not full remount.** Navigating between sibling routes preserves parent components. Only the changed level is remounted. 4. **No `for..in` on objects.** Use `Object.keys()` with an indexed `for` loop. `for..in` walks the prototype chain (slow) and requires an `Object.hasOwn` guard to be safe — `Object.keys` is both faster and prototype-safe in one call: ```typescript // ✗ Bad: slow, prototype-unsafe without guard for (const key in obj) { ... } // ✗ Still bad: correct but slower than Object.keys for (const key in obj) { if (Object.hasOwn(obj, key)) { ... } } // ✓ Good: fast, prototype-safe, no guard needed const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { ... } ``` ### Memory 1. **Release unused templates.** `Router.gc()` clears cached component templates for routes the user hasn't visited recently. Active route components are never released. 2. **Inventory bitmask prevents duplicate downloads.** The server tracks which component templates the client already has via a bitmask. Re-navigation never re-sends templates. 3. **Minimal state per navigation.** Route-scoped state means the JSON partial contains only what the active route needs, not the full app state. ## Measuring ### Server benchmarks ```bash cargo bench -p microsoft-webui --bench contact_book_bench # full run cargo bench -p microsoft-webui --bench contact_book_bench -- --test # quick validation cargo bench -p microsoft-webui --bench server_request_bench # high-level request helper cargo xtask bench all # all Rust crates cargo xtask bench node-addon # Node/V8/N-API boundary ``` Compare **Render/1000 P50** before and after. For Node changes, save with `--save-baseline before` and compare with `--baseline before`. Verify output **Bytes** is unchanged (same HTML = correct behavior). ### Client performance ```typescript window.addEventListener('webui:hydration-complete', () => { for (const entry of performance.getEntriesByType('measure')) { if (entry.name.startsWith('webui:hydrate:')) { console.log(`${entry.name}: ${entry.duration.toFixed(1)}ms`); } } }); ``` ### What to report When making a performance-related change, report: - **Before/after benchmark numbers** (P50 latency, throughput) - **Allocation count delta** if measurable - **Output size** unchanged (proves correctness) - **Memory profile** for memory-related changes (heap snapshots, RSS delta)
Voir sur GitHub