Skip to main content

neverd-performance

Keep NeverD fast without changing what it computes. Use when writing or reviewing code on a hot path (loaders, decode/lift, Low/Med/High IR passes, signature matching, symbolic/MBA proofs, emulation, worker/GUI latency), choosing a container or index, adding a parallel loop, SIMD, cache, arena or threshold, evaluating a performance library, or claiming or reviewing a speedup or regression. Use when the user runs /neverd-performance.

Source facts

Repository
NeverSight/NeverD
Last source activity
October 30, 2026 at 14:37
Detected SKILL.md language
English
Stars
100
Forks
19

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
3 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
neverd-performance
description
Keep NeverD fast without changing what it computes. Use when writing or reviewing code on a hot path (loaders, decode/lift, Low/Med/High IR passes, signature matching, symbolic/MBA proofs, emulation, worker/GUI latency), choosing a container or index, adding a parallel loop, SIMD, cache, arena or threshold, evaluating a performance library, or claiming or reviewing a speedup or regression. Use when the user runs /neverd-performance.
# NeverD performance NeverD is semantics-first and must also be fast. A fast path computes exactly what the portable path computes, and every speed claim is measured. Basis: `origin/dev` 159588db82 for the inventory and 92b7f8dab1 for the profiles (2026-10-27). `dev` moves quickly; re-check a path before relying on a line number. - What exists today: [references/current-state.md](references/current-state.md) - What to do next, and what was rejected: [references/remaining-work.md](references/remaining-work.md) This skill routes; these documents own the rules: | Topic | Owner | | --- | --- | | Acceleration libraries, allocator, build options | `CONTRIBUTING.md` § Process allocator, § Performance libraries | | Thresholds, budgets, block sizes, cache retention | `CONTRIBUTING.md` § Limits, defaults and configuration ownership | | Where measurements live | `CONTRIBUTING.md` § Documentation and local artifacts | | Acceleration and allocator validation | `docs/testing.md` § Acceleration libraries, § Allocator validation | | No just-in-case machinery | `docs/development-principles.md` § Speculative defenses | | Library evaluation example | `docs/bbtree-evaluation.md` | ## Rules for new code 1. **Complexity before constants.** Do not scan all functions, blocks, calls or instructions per query. Build an index once per phase, function or image, for example blocks by id (`e37aec14d1`). In loops over functions, calls or registers, do not rebuild a derived structure per call: build it once and key it on every input it depends on (`7d192005b5` keys on target and format). Before an expensive build or proof, rule out the functions it cannot change with a cheap precondition (`772f5b2283`, `71208d60e9`). For example, `buildHighSourceFlowGraph` builds a whole guarded graph each time a pass calls it. 2. **Skip unread work; reuse proven facts.** Defer reconstruction until a consumer asks (`41fe5c5f96`, `a23c299f2a`), stop a rewrite loop that makes no progress (`1d0ce93dc2`), and reuse validated results across selections (`592434ba3f`). Recompute when a mutation can invalidate them, for example symbol edits. 3. **Choose containers by operation.** | Access pattern | Use | | --- | --- | | Small sequence, worklist | `llvm::SmallVector` | | Exact-key lookup | `llvm::DenseMap` / `DenseSet`; reserve when the count is known (`739fc32a3c`) | | Unsigned 64-bit keys, built once, queried many times | sorted `std::vector<uint64_t>` plus `neverd::sorted::lowerBound64` / `contains64` | | Ordered mutation interleaved with ordered queries | `std::map` / `std::set` | | Spatial query over rectangles | the worker's BVH pattern in `tools/neverd-worker/GraphSnapshot.cpp` | Hashed and parallel results never decide output order. Sort, or merge by index, before emitting. 4. **Reuse the existing fast primitives** (table below) before writing a new one. A new primitive or library needs an evaluation like `docs/bbtree-evaluation.md` first: fit, alternatives, cost model, measurement scope and validation boundaries. 5. **SIMD goes through Highway** with `HWY_DYNAMIC_DISPATCH`. Raw intrinsics are only for operations Highway lacks (SHA extensions, for example), behind a host feature check with a portable fallback (`lib/support/SHA256.cpp`). - Keep short inputs on an inline scalar path. - Never read outside the input: no padding reads and no over-reads at EOF. - Add no software prefetch, retained state or locks without measured evidence. - Test every Highway target, `NEVERD_ENABLE_HIGHWAY=OFF`, unaligned input and input ending at an inaccessible page. - When the design depends on branch-free code, check the disassembly. Clang's `__builtin_unpredictable` must appear in the conditional expression itself. 6. **Parallelism uses `neverd::parallelForEach`** or `parallelForEachWeighted`. Use the weighted form, heaviest first, when element costs are skewed. - Each worker owns the slots it writes. Shared containers are sized before the batch and never resized inside it. - Keep small batches serial. Their thresholds belong in the owning limits (`limits::kMinParallelIRWorkItems`, `SignatureLimits.def`). - Do not call oneTBB or start raw `std::thread` workers. Respect `workerThreadCount()` and `NEVERD_THREADS`. - Nested batches are legal but drain on a fresh native stack, so avoid them on hot inner paths. - Do not add whole-image or whole-selection work on the caller's thread. Per-function work belongs inside the existing `parallelForEachWeighted` batches. Interprocedural work needs a stated parallel plan, or a stated reason it cannot have one. Half or more of a profiled run's wall time already passes with one or two busy threads (`references/remaining-work.md`). 7. **Allocation.** Reserve when sizes are known, avoid per-element heap allocation in hot loops, and use a phase-scoped arena when lifetimes match (`std::pmr::monotonic_buffer_resource` in `lib/ir/low/jumptable/JumpTableResolverSlice.cpp`). Allocator replacement stays in executables: the SDK never imposes one. 8. **I/O and serialization.** - Map inputs with `RequiresNullTerminator=false` (`caf7af4f5c`). - Stream large JSON instead of building a whole DOM (`d75ead04bc`). - Batch repeated lookups (`a89f54a3d3`). - Read typed rows from the validated simdjson tape (`a5f0527ba5`). 9. **Policy values live in the owning limits header**, including compiler-specific values. The `SortedSearch` block in `include/neverd/support/Limits.h` is the model: each value carries a comment giving its reason. 10. **Same output under every configuration**: any thread count, acceleration on or off, either allocator. A fast path that cannot decide falls back to the exact path. It never approximates or guesses. ## Which existing facility | Need | Use | Where | | --- | --- | --- | | Lower bound or membership on sorted u64 keys | `neverd::sorted::lowerBound64`, `contains64` | `include/neverd/support/SortedSearch.h` | | Length of an ASCII text run | `neverd::strings::detail::textASCIIPrefix` | `include/neverd/support/ByteScan.h` | | Whole-buffer UTF-8 fast path | `neverd::knownValidUTF8`; `false` means "take the exact path", not "invalid" | `include/neverd/support/ByteScan.h` | | Parallel independent items | `parallelForEach`, `parallelForEachWeighted` | `include/neverd/support/Parallel.h` | | Transport or MCP JSON | `parseJson` | `tools/common/transport/Json.h` | | Patch JSON rows | simdjson DOM tape reader | `lib/patch/PatchJSON.cpp` | | Content hash for a cache key | `llvm::xxh3_64bits` | `lib/sigs/SignatureCache.cpp` | | SHA-256 digest | `neverd::sha256` | `include/neverd/support/SHA256.h` | | Input bytes | `llvm::MemoryBuffer::getFile(..., /*RequiresNullTerminator=*/false)` | `lib/loader/Loader.cpp` | ## Measuring 1. **Profile first.** Run a sampling profiler on a warm Release run and optimize what the profile shows. Where `perf` is unavailable, use `tools/neverd-bench/sigprof` (see its README); `NEVERD_NATIVE_PHASES=1` prints each pipeline stage's elapsed time. - Record the hotspot's CPU share **and** its serial wall share: the time, by phase, during which at most two threads were busy (`report.py` prints it). - A phase with a small CPU share that runs on one thread can still take a large part of the wall time on a many-core host. 2. **Same-base A/B.** Build baseline and candidate from the same `origin/dev` commit. Use Release, the same compiler, the same dependency snapshot (`neverd-dependency-inputs.json`), the same allocator and the same `NEVERD_THREADS`. 3. **Alternate A and B runs** over many repetitions, and fix CPU affinity where possible. Report user+sys CPU and wall time separately. On a shared host, CPU time is the primary signal for compute work; wall time is the signal for latency and scaling. 4. **Hash all non-timing output** from both binaries. Any difference blocks the change. 5. **Measure both levels.** Measure the primitive and the whole request. A primitive win that does not reach the request is not a claim. 6. **Cover the configurations** the change touches: default and constrained threads (`NEVERD_THREADS=1`), `NEVERD_ENABLE_{HIGHWAY,SIMDUTF,SIMDJSON,TBB}=OFF`, and `NEVERD_ENABLE_MIMALLOC=OFF`. 7. **Keep raw results local.** Put raw samples, traces and summaries in ignored build directories. In the PR, give the base commit, commands, input hashes, host and paired CPU deltas. Do not commit timing tables. ```bash # Whole pipeline, bounded; repeat alternately for A and B. /usr/bin/time -f '%U user %S sys %e wall %M KiB' \ build-a/bin/neverd decompile IMAGE --max-func=250 -o build-a/out.c sha256sum build-a/out.c build-b/out.c ``` Primitive benchmarks are opt-in (`EXCLUDE_FROM_ALL`) targets: - `neverd-acceleration-bench`, and `neverd-sorted-search-bench` with modes `std`, `simd`, `std-contains`, `contains` and `owner`. Both are in `unittests/acceleration`. - `neverd-signature-bench`, `neverd-med-bench`, `neverd-cpu-bench` and `neverd-solver-bench` in `tools/neverd-bench`. - `neverd-viewport-bench` and `pseudocode_latency_bench.py` in `tools/neverd-gui/benchmarks`. Correctness owners are `NeverDAccelerationTests`, plus the owners listed in `docs/testing.md` § Acceleration libraries. After merging someone else's PR, re-check performance the same way: compare `dev` with the merge reverted against `dev` with the merge, on the same base. Push follow-up fixes when the merge regresses. ## Review checklist - [ ] The profile names the hotspot; the A/B uses the same base; output hashes match. - [ ] No per-query scan or per-call rebuild inside loops over functions, blocks or calls. - [ ] No new whole-image or whole-selection work on the caller's thread; the serial wall share did not grow. - [ ] The scalar/portable path remains and is tested with acceleration off; SIMD covers every Highway target and inaccessible tails. - [ ] Every new threshold or budget lives in its owning limits definition, with a reason. - [ ] Output is identical across thread counts; hashed and parallel results are ordered before emission. - [ ] Each cache is keyed on all its inputs, invalidated on mutation, and has bounded retention in limits. - [ ] No speculative caches, prefetches, retries or fallbacks. - [ ] A new library or primitive has an evaluation document, a NeverSight fork if vendored, notices, and a `NEVERD_ENABLE_*` opt-out.
View on GitHub