Skip to content

Perf pass 2: benchmark the uncovered operation surface, then fix what it exposed#21

Merged
NiceAndPeter merged 4 commits into
mainfrom
perf-pass-2
Jul 19, 2026
Merged

Perf pass 2: benchmark the uncovered operation surface, then fix what it exposed#21
NiceAndPeter merged 4 commits into
mainfrom
perf-pass-2

Conversation

@NiceAndPeter

Copy link
Copy Markdown
Owner

What

Follow-up to #18/#19/#20: the bench had no rows at all for sub, mod, negation, compound `-=`/`*=`, min/max/midpoint/lerp/cross, wrap_cast, sentinel stores, `==`, conversions out, f64-backed arithmetic, the exact/rational path, `bound_range` iteration, and several `bnd::math` functions. This PR adds ~30 rows covering all of that, then optimizes the three paths the new rows proved slow — and fixes a correctness bug the new tests flushed out.

Measured wins (ins/op, GCC -O3)

path before after
`bound_range` iteration, integer grid 1286 146 (8.8×)
`bound_range` iteration, 1/256 grid 1609 145 (11×)
`-=` on Q8.8 (index-raw grids) 806 468 (residue is operand ctor)
Q8.8 `==` int scalar 677 139 (native parity)
  • bound_range: `operator*` decoded every element through rational + the assignment engine; index storage now returns the iterator index as the raw, integer value storage is one multiply-add, and single-step `++`/`--` wrap by compare instead of a euclidean mod. Rational/fp raws keep the generic path.
  • `-=`: delegating to `+= (-rhs)` shifted an index-raw operand's Lower and defeated both `+=` fast paths; same-notch integer-backed operands now subtract raws with a compile-time bias, reusing `apply_raw_overflow`.
  • scalar compare: Q-format vs integral scalar compares as cross-multiplied integers, gated on both terms provably fitting `imax` for every value of the scalar type (64-bit scalars with large denominators keep the rational decode).
  • f64-backed add/mul: bench row looked 2× native, but the disassembly is a single `addsd`/`mulsd` — parity pinned with a codegen kernel instead.

Correctness fix (latent bug found by the new fallback tests)

Cross-grid assignment onto a rational-raw target used the notch-index machinery: an index-raw source's value was rounded to a whole number (7/3 → 2/1) and a rational-raw source's raw — already the value — had the grid transform applied again (5/3 → corrupted `3/-1`). Reproduces on main; rational targets now snap the decoded source value through the rational-rhs store. Regression cases in test_cross_grid.

Guards

  • Codegen guard: three new call-free kernels (`bnd_perf_sub_compound`, `bnd_perf_fp_add`, `bnd_perf_range_sum`).
  • `[perf-paths]` decode-equality + dispatch pins for every bound_range storage kind; `-=` old-vs-new value cross-checks incl. clamp/wrap/sentinel/checked at the raw edges; scalar-compare agreement with the rational oracle at numeric_limits extremes.
  • No new cachegrind keys (would need CI-artifact bootstrap; nanobench ins/op + codegen guard cover these paths).

Verification

  • ctest: default 439/439, fixed 428 cases, float 385, sanitizer 394 — all pass.
  • `accuracy_report`: bit-identical.
  • `docs/performance.md` regenerated; amalgamation refreshed.

🤖 Generated with Claude Code

Peter Neiss and others added 4 commits July 19, 2026 14:55
New nanobench groups for operations that had no row at all: binary sub
(u8 / signed / Q8.8), mod, unary negation, compound -= / *= / += rational,
min/max (same + mixed grid), midpoint, lerp, cross, add_all/mul_all,
wrap_cast, sentinel stores, comparison == (incl. bound vs raw scalar),
conversions out of bound, f64-backed add/mul, the exact/rational path
(exact-backed add/mul/div/compare, mixed exact + integer add, and
detail::rational engine ops vs a hand-rolled reduced int64 fraction),
bound_range iteration, and cmath ceil/atan/acos/sinh/log10/pown/pow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
operator* built Lower + index*Notch in rational and ran the full
assignment engine for every element. Index storage now returns the
iterator index as the raw directly (it stays in [0, NotchCount]; the
sentinel slot is a numeric_limits extreme outside that span), and
integer-grid value storage decodes with one multiply-add; the start
ctor inverts the same mapping. Single-step ++/-- wrap by compare
instead of paying the euclidean mod's two divides. Rational and fp
raws keep the generic path.

Bench: 1286 -> 146 ins/op (integer grid), 1609 -> 145 (1/256 grid).
Guards: decode-equality across every storage kind + dispatch pins in
test_range.cpp ([perf-paths]).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
-= delegated to += (-rhs), but negation shifts an index-raw operand's
Lower and defeats both += fast paths, sending Q8.8-style grids through
the full binary add + cross-grid assign. With equal notches the raw
difference is exact up to a compile-time bias (Lower<R>/Notch for an
index-raw rhs), reusing apply_raw_overflow for the policy tail.
Bench: Q8.8 -= 806 -> 468 ins/op (the residue is operand construction).

The new fallback test exposed a latent bug: the boundable-rhs store
used the notch-index machinery for rational-raw TARGETS, rounding an
index-raw source's value to a whole number (7/3 -> 2/1) and applying
the grid transform to an exact source's raw that already was the value
(5/3 -> corrupted). rational targets now snap the decoded source value
through the rational-rhs store. Regression cases in test_cross_grid.

Also: Q-format vs integral-scalar ==/<=> compare as cross-multiplied
integers ((bias + raw)*n vs c*d, exact both ways) when both terms
provably fit imax for every value of the scalar type; 64-bit scalars
with large denominators fall back to the rational decode.
Bench: Q8.8 == int 677 -> 139 ins/op (native parity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New call-free kernels: bnd_perf_sub_compound (Q8.8 -=), bnd_perf_fp_add
(f64-backed add lowers to one addsd — proven parity; the bench row's 2x
is loop-harness noise), bnd_perf_range_sum (both bound_range decode
arms). docs/performance.md regenerated with the new groups; amalgamation
refreshed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NiceAndPeter
NiceAndPeter merged commit 1fc8a2c into main Jul 19, 2026
21 checks passed
@NiceAndPeter
NiceAndPeter deleted the perf-pass-2 branch July 19, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant