Skip to main content

perf

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

소스 정보

저장소
microsoft/webui
최근 소스 활동
2026년 8월 26일 04:54
감지된 SKILL.md 언어
영어
스타
98
포크
24

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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)
GitHub에서 보기