mirror of
https://github.com/bdbrown4/peltier.io.git
synced 2026-11-07 10:58:28 +00:00
Bootstrap hotpath from engineering spec v0.1
Commit the spec and scaffold the system it describes:
- SPEC.md: the full v0.1 engineering spec
- crates/: trust-layer Rust workspace, unit-tested
- bench-runner: interleaved ABAB scheduling with discarded warm-ups,
deterministic bootstrap CIs over the ratio of medians,
CI-lower-bound accept rule, env fingerprinting, and a CLI with
compare + A/A self-test modes (A/A verified passing end-to-end)
- diff-test: gate outcome model, sha256 corpus pin verifier that
refuses on mismatch, per-target equivalence policy parser
(byte-identical default, explicit fp-tolerance opt-in)
- ledger: append-only SQLite attempt schema with UPDATE/DELETE
refused by triggers, anti-double-attempt query
- report: cores-saved and dollars/yr ROI math over speedup CIs,
pricing.toml inputs
- playbook/: classes 1-7 with preconditions, procedure, verification
notes, and known failure modes per class
- agent/: Python package skeleton — six-tool contract and prompting
spine, harness IPC deferred to Phase 1
- config/: accept.toml (2% threshold, 30 runs/side), pricing.toml
- targets/, corpora/, results/calibration/: layout and policy docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcKPWQ3MQD4NdEHwar96u2
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Class 1 — Build configuration
|
||||
|
||||
Zero source changes. Always first: a large fraction of real-world CPU is
|
||||
lost to shipping defaults.
|
||||
|
||||
## Preconditions
|
||||
- Any CPU-bound target not already using an optimized release profile.
|
||||
- Profile signature: flat-ish flamegraph with time spread across many
|
||||
small frames (inlining starved); heavy call overhead into tiny leaf
|
||||
functions; or a build that visibly lacks `-O2`/`--release`.
|
||||
- Check before anything else: `opt-level`, `lto`, `codegen-units`,
|
||||
`panic`, `target-cpu` (Rust); `-O`, `-march`, `-flto` (C/C++).
|
||||
|
||||
## Procedure (cheapest first)
|
||||
1. Confirm release profile: `opt-level=3`, `panic=abort` where the target
|
||||
permits, `codegen-units=1`.
|
||||
2. `target-cpu=native` / `-march=native` — only when the deployment
|
||||
fleet's ISA baseline is known; record the chosen baseline in the
|
||||
hypothesis.
|
||||
3. LTO: thin, then fat. Watch build-time cost; fat LTO on huge targets
|
||||
can be a net loss for the engagement.
|
||||
4. PGO: instrument with the golden-replay corpus as the training run,
|
||||
rebuild with the profile. The corpus is representative by
|
||||
construction.
|
||||
5. BOLT (post-link layout) where the binary and toolchain permit.
|
||||
|
||||
## Verification notes
|
||||
- Flags must not include any `-ffast-math`-class option — that's an
|
||||
equivalence change, auto-routed to `needs-human-review` (SPEC §8).
|
||||
- Byte-identical golden replay still applies: build flags occasionally
|
||||
change FP contraction on some ISAs (`-march` enabling FMA); if golden
|
||||
replay diverges on numeric output, stop — this class is not
|
||||
equivalence-preserving for that target without a tolerance policy.
|
||||
|
||||
## Known failure modes
|
||||
- `target-cpu=native` on the bench machine but not the fleet: a real win
|
||||
that the customer cannot deploy. Pin the ISA baseline to the fleet.
|
||||
- PGO trained on a non-representative workload overfits the benchmark —
|
||||
hold out workloads (SPEC §11).
|
||||
- Layout changes from LTO/BOLT interact with layout bias (§7): measured
|
||||
wins here *especially* need the randomize-and-aggregate protocol.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Class 2 — Allocator swap
|
||||
|
||||
Link-line only. With class 1, forms the "your 30% was a linker flag"
|
||||
tier that is always exhausted before touching source.
|
||||
|
||||
## Preconditions
|
||||
- Profile signature: `malloc`/`free`/`operator new` or allocator-internal
|
||||
symbols (`_int_malloc`, `tcache_*`, page-fault handling) ≥5% of
|
||||
exclusive samples; or high syscall time in `mmap`/`brk`; or
|
||||
multithreaded target with allocator lock contention visible.
|
||||
|
||||
## Procedure
|
||||
1. Rust: add `mimalloc` (then `jemallocator`) as `#[global_allocator]` —
|
||||
two-line diff, still effectively link-level.
|
||||
2. C/C++: `LD_PRELOAD` trial first (free experiment, no rebuild), then
|
||||
proper linking of the winner.
|
||||
3. Bench each candidate allocator separately; do not stack with other
|
||||
changes.
|
||||
|
||||
## Verification notes
|
||||
- Behavior-preserving by contract, so gates should pass trivially — but
|
||||
run them all anyway: allocator swaps change address-space layout and
|
||||
can unmask latent UB (use-after-free that "worked" under glibc). A
|
||||
sanitizer failure here is a pre-existing target bug: record it,
|
||||
route to `needs-human-review`, do not ship.
|
||||
- Max RSS is captured per run: an allocator that wins time but doubles
|
||||
memory is a trade-off the report must state, not hide.
|
||||
|
||||
## Known failure modes
|
||||
- Wins on the bench box's core count that vanish (or invert) at the
|
||||
fleet's concurrency level.
|
||||
- `LD_PRELOAD` trial passing but static-link integration subtly failing
|
||||
(symbol interposition differences) — re-run full gates on the final
|
||||
link, not the trial.
|
||||
- Fragmentation pathologies on long-running services that a short bench
|
||||
window never sees; service-mode targets need soak evidence (Phase 4).
|
||||
@@ -0,0 +1,36 @@
|
||||
# Class 3 — Allocation churn
|
||||
|
||||
First source-touching class: reduce the *number* of allocations, not the
|
||||
allocator servicing them.
|
||||
|
||||
## Preconditions
|
||||
- Class 2 helped but allocator symbols still hot, or allocation shows up
|
||||
as cache misses from pointer-chasing freshly allocated objects.
|
||||
- Profile signature: allocation inside hot loops; `Vec`/`String`
|
||||
reallocation churn; many short-lived objects of the same type.
|
||||
|
||||
## Procedure (cheapest first)
|
||||
1. Hoist allocations out of loops; reuse buffers via `clear()` instead
|
||||
of re-creation.
|
||||
2. `Vec::with_capacity` / `reserve` where the size is known or bounded.
|
||||
3. Small-vector types (`smallvec`, fixed inline buffers) for collections
|
||||
that are almost always tiny — confirm the size distribution from the
|
||||
profile or a counting instrument first.
|
||||
4. Arena allocation (`bumpalo`, typed arenas) for phase-scoped object
|
||||
graphs with a clear reset point.
|
||||
|
||||
## Verification notes
|
||||
- Buffer reuse is where stale-data bugs come from: golden replay must
|
||||
cover inputs of *decreasing* size across a session so a dirty buffer
|
||||
would surface. Add such a sequence to the corpus if missing.
|
||||
- Arenas change drop order; targets with observable `Drop` side effects
|
||||
(logging, flushing) can diverge on golden replay — that divergence is
|
||||
a real reject, not noise.
|
||||
|
||||
## Known failure modes
|
||||
- `with_capacity` guesses that overshoot: RSS regression the wall-clock
|
||||
number won't show — check the captured max RSS.
|
||||
- smallvec inline sizes chosen by vibes: an inline buffer bigger than
|
||||
the cache line it saves. Measure, don't guess.
|
||||
- Reuse introducing borrow-checker contortions that force `unsafe` —
|
||||
`unsafe` introduction routes to `needs-human-review` (SPEC §8).
|
||||
@@ -0,0 +1,37 @@
|
||||
# Class 4 — Data layout
|
||||
|
||||
Make the memory hierarchy work for the access pattern.
|
||||
|
||||
## Preconditions
|
||||
- Profile signature: high cache-miss rate in `perf stat` (LLC misses per
|
||||
instruction), hot loops whose flamegraph frames are memory-bound
|
||||
(`perf` cycles concentrated on loads), low IPC (<1) on wide cores.
|
||||
- For false sharing: multithreaded target where per-thread throughput
|
||||
degrades as threads are added; `perf c2c` confirms.
|
||||
|
||||
## Procedure (cheapest first)
|
||||
1. Struct field reordering / packing: kill padding, group hot fields on
|
||||
one cache line, split hot/cold (`#[repr(C)]` review, cold-field
|
||||
box-out).
|
||||
2. AoS→SoA for loops that touch one field across many elements —
|
||||
restructure the container, not the algorithm.
|
||||
3. False-sharing fixes: pad or align per-thread state to 64 bytes
|
||||
(`#[repr(align(64))]`, `crossbeam_utils::CachePadded`).
|
||||
4. Cache blocking / loop tiling for nested loops over large matrices or
|
||||
images; tile to L1/L2, verified empirically, not by datasheet.
|
||||
|
||||
## Verification notes
|
||||
- Layout changes to any type that crosses FFI, serialization, or hashing
|
||||
boundaries can change observable behavior (struct hash, serialized
|
||||
bytes, memcmp-based equality) — golden replay catches this; check the
|
||||
target for `#[repr]`-sensitive traits before patching.
|
||||
- Differential fuzz the functions whose types changed, not just the
|
||||
functions whose code changed.
|
||||
|
||||
## Known failure modes
|
||||
- Reordering fields of a type that derives `Hash`/`Ord` used in ordered
|
||||
iteration → output order changes → replay divergence (correct reject).
|
||||
- AoS→SoA wins the target loop but loses every other access site; the
|
||||
end-to-end number is the verdict, not the microbench.
|
||||
- Alignment padding blowing up per-element size until the working set
|
||||
falls out of cache — the cure worse than the disease.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Class 5 — Algorithmic
|
||||
|
||||
Complexity class and primitive selection. Highest variance: the biggest
|
||||
wins live here, and so do the most seductive wrong ideas.
|
||||
|
||||
## Preconditions
|
||||
- Profile signature: one dominant frame that is *algorithm*, not memory
|
||||
(high IPC, low miss rate, still slow); superlinear scaling of runtime
|
||||
with input size; hot `contains`/`find` over linear structures; repeated
|
||||
recomputation of pure functions on repeated arguments.
|
||||
|
||||
## Procedure (cheapest first)
|
||||
1. Better std/library primitive: `HashMap` for linear scans, `sort_unstable`
|
||||
over `sort`, `entry()` API over get-then-insert, buffered I/O,
|
||||
`memchr`-class scanning.
|
||||
2. Precomputation: hoist invariant work out of loops; build lookup tables
|
||||
at startup when the domain is small.
|
||||
3. Memoization for pure hot functions with high argument repeat rates —
|
||||
confirm the repeat rate from a counting instrument before adding a
|
||||
cache.
|
||||
4. Complexity-class replacement (the real prize): O(n²) → O(n log n)
|
||||
with a known-correct algorithm. Prefer a well-tested library
|
||||
implementation over a hand-rolled one, always.
|
||||
|
||||
## Verification notes
|
||||
- HashMap iteration order is not deterministic order: if results flow
|
||||
from iteration order into output, replay diverges — either sort at the
|
||||
boundary or reject the change.
|
||||
- Memoization + interior mutability in multithreaded targets touches the
|
||||
concurrency review tier (SPEC §8).
|
||||
- Differential fuzz is load-bearing here: algorithmic replacements have
|
||||
edge-case surfaces (empty input, duplicates, already-sorted, unicode)
|
||||
where "equivalent" algorithms differ. 10k iterations minimum, seeded
|
||||
with corpus-derived cases.
|
||||
|
||||
## Known failure modes
|
||||
- Asymptotic win, constant-factor loss at real input sizes — the ledger
|
||||
records the workload; the verdict is measured at that workload.
|
||||
- Caches without bounds: memoization that trades a 10% speedup for
|
||||
unbounded RSS growth. Check max RSS; bound the cache.
|
||||
- "Equivalent" floating-point reformulations that aren't (different
|
||||
rounding paths) — FP output changes route to the tolerance policy or
|
||||
human review, never silently absorbed.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Class 6 — SIMD
|
||||
|
||||
Autovectorization enablement first, intrinsics last (SPEC §4).
|
||||
|
||||
## Preconditions
|
||||
- Profile signature: hot, trip-count-heavy inner loops over contiguous
|
||||
numeric/byte data; scalar math or byte-wise processing in the top
|
||||
frames; disassembly of the hot loop shows scalar ops where vector ops
|
||||
are plausible.
|
||||
|
||||
## Procedure (cheapest first)
|
||||
1. Read the optimizer's mind: check the hot loop's assembly (or
|
||||
`-Cremark`/`-Rpass=loop-vectorize`) for *why* vectorization failed.
|
||||
2. Remove the blocker, in order of cheapness:
|
||||
- Bounds checks: restructure with slices/iterators or hoist a single
|
||||
length check (`&x[..n]` pattern) — not `unsafe` indexing.
|
||||
- Aliasing: split borrows, `split_at_mut`, pass slices instead of
|
||||
overlapping raw pointers.
|
||||
- Loop-carried dependencies: reassociate integer reductions, unroll
|
||||
accumulators.
|
||||
3. Widen the ISA baseline if class 1 hasn't already (`target-cpu`), since
|
||||
SSE2-only baselines strangle the vectorizer.
|
||||
4. `std::simd` / portable-SIMD explicit vectorization.
|
||||
5. Raw intrinsics only when all of the above fail and the win is large;
|
||||
isolate behind a scalar-fallback path selected at runtime.
|
||||
|
||||
## Verification notes
|
||||
- **FP flags are a hard line**: any `-ffast-math`-class flag, FP
|
||||
reassociation, or FMA-contraction change routes to
|
||||
`needs-human-review` — no exceptions, even for a 2x win (SPEC §8).
|
||||
Integer/byte SIMD has no such taint and can auto-accept.
|
||||
- Intrinsics imply `unsafe` → automatic `needs-human-review` + MIRI
|
||||
where feasible + differential fuzz against the scalar path with
|
||||
adversarial lengths (0, 1, lane-1, lane, lane+1, unaligned offsets).
|
||||
|
||||
## Known failure modes
|
||||
- Tail handling: the classic SIMD bug class. Fuzz lengths around lane
|
||||
boundaries explicitly.
|
||||
- Alignment assumptions that hold on the bench box and fault (or crawl)
|
||||
elsewhere.
|
||||
- Runtime feature detection done per-call in the hot loop, eating the
|
||||
win; hoist detection to init.
|
||||
- Integer reduction reassociation is safe; float reduction reassociation
|
||||
changes results — do not confuse the two.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Class 7 — Concurrency
|
||||
|
||||
Last in the order, highest risk. **Every diff in this class routes to
|
||||
`needs-human-review`** (SPEC §8); the agent proposes, a human disposes.
|
||||
|
||||
## Preconditions
|
||||
- Profile signature: multithreaded target where wall time ≫ ideal
|
||||
(cpu-time / cores); lock symbols (`futex`, `pthread_mutex`,
|
||||
`parking_lot`) hot; run-queue latency; per-thread throughput that
|
||||
*drops* as threads are added. coz (Phase 3) is the right prioritizer
|
||||
here — cycle profiles mislead on blocked time.
|
||||
|
||||
## Procedure (cheapest first)
|
||||
1. Shrink critical sections: move allocation, I/O, and computation out
|
||||
from under locks; drop guards early.
|
||||
2. Reduce sharing: per-thread accumulation + merge at the end beats a
|
||||
shared counter; sharding a hot map by key hash beats a fancier lock.
|
||||
3. Right-size the primitive: `RwLock` for read-mostly, `OnceLock` for
|
||||
init-once, atomics for simple counters *with explicitly justified
|
||||
orderings* (start from `SeqCst`; relax only with a written argument).
|
||||
4. Lock-free structures: last resort, library-only (crossbeam), never
|
||||
hand-rolled, and only when the profile proves the lock is the
|
||||
bottleneck after 1–3.
|
||||
|
||||
## Verification notes
|
||||
- TSan gate is mandatory for this class, on both the upstream suite and
|
||||
the golden replay run (SPEC §3.2).
|
||||
- Thread-count sweep: gates and bench at 1, 2, N, and 2N threads —
|
||||
concurrency bugs and wins are both thread-count-dependent.
|
||||
- Nondeterministic output ordering (e.g. results gathered from threads)
|
||||
must be canonicalized at the boundary or the change rejected; replay
|
||||
equivalence is not negotiable to make a parallel win look clean.
|
||||
|
||||
## Known failure modes
|
||||
- Benchmarks too short to hit the contention window: a race that fires
|
||||
once per million ops passes 60s of fuzz. TSan + long soak, and still
|
||||
route to a human.
|
||||
- Relaxed atomics that are correct on x86's strong model and wrong on
|
||||
ARM — orderings need an argument, not a test pass.
|
||||
- Sharding that wins the benchmark's uniform key distribution and loses
|
||||
production's skewed one — holdout workloads (SPEC §11).
|
||||
- Deadlocks introduced by lock-order changes that no gate exercises;
|
||||
the human reviewer checks lock ordering explicitly.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Optimization playbook v0
|
||||
|
||||
Classes are tried in **strict order** (SPEC §4) — cheapest first. Classes
|
||||
1–2 are the "your 30% was a linker flag" tier and are always exhausted
|
||||
before any source change. Each class documents:
|
||||
|
||||
- **Preconditions** — the profile signature that suggests it applies
|
||||
- **Procedure** — what to actually try, cheapest variant first
|
||||
- **Verification notes** — gate interactions specific to this class
|
||||
- **Known failure modes** — where this class produces fake wins or real bugs
|
||||
|
||||
| # | Class | Touches source? | Human review? |
|
||||
|---|-------|-----------------|---------------|
|
||||
| 1 | [Build configuration](01-build-config.md) | no | only PGO/BOLT pipeline changes |
|
||||
| 2 | [Allocator swap](02-allocator.md) | link line only | no |
|
||||
| 3 | [Allocation churn](03-allocation-churn.md) | yes | no |
|
||||
| 4 | [Data layout](04-data-layout.md) | yes | no |
|
||||
| 5 | [Algorithmic](05-algorithmic.md) | yes | no |
|
||||
| 6 | [SIMD](06-simd.md) | yes | FP-flag changes: always |
|
||||
| 7 | [Concurrency](07-concurrency.md) | yes | **always** |
|
||||
|
||||
The agent's prompting spine (SPEC §3.5): state the hypothesis before
|
||||
patching; prefer the cheapest untried class; max two iterations per
|
||||
rejected hypothesis; a rejection with a clean ledger row is a successful
|
||||
outcome.
|
||||
Reference in New Issue
Block a user