Skip to content

Commit 934d9a3

Browse files
committed
perf: specialize single-limb wide raw ratios
1 parent 28aef44 commit 934d9a3

8 files changed

Lines changed: 732 additions & 29 deletions

File tree

docs/complexity-exceptions.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ complexity review threshold.
1515
score cannot fall below its cyclomatic complexity, so documented complexity
1616
floors are reviewed rather than mechanically refactored.
1717
- Current status: the latest full-project run, excluding generated serializer
18-
sources, covers 47,061/47,061 lines and 8,694/8,694 branches. The CRAP
19-
analyzer scored 3,415 method identities; every method is fully covered and
20-
the only scores above 30 are the ten registered complexity floors at 52, 48,
21-
48, 44, 44, 38, 36, 32, 32, and 32.
18+
sources, covers 53,003/53,003 lines, 8,768/8,768 branches, and
19+
3,411/3,411 coverage methods. The CRAP analyzer scored 3,407 method
20+
identities; every analyzed method is fully covered, and the only nine scores
21+
above 30 are the registered complexity floors at 52, 48, 48, 44, 44, 38,
22+
36, 32, and 32.
2223
Per-method coverage is recorded below and should be refreshed whenever a
2324
listed implementation changes.
2425

@@ -44,7 +45,7 @@ behavior changes, or the implementation becomes harder to reason about.
4445
| `FixedMathSharp` | `Fixed4x4.Equals(Fixed4x4)` | 30 | 100% line / 100% branch | Direct 4x4 value comparison avoids loops, allocations, and indexer overhead on a hot value type. | Equality semantics change or a generated/source-shared component comparison becomes available without runtime cost. |
4546
| `FixedMathSharp` | `FixedSegment.GetClosestPoints(FixedSegment)` | 26 | 100% line / 100% branch | Full-domain setup, exact point-degeneracy handling, and bit-exact endpoint preservation remain at the public query boundary. | Endpoint identity can move into a simpler shared primitive without extra wide products or an ordinary-input regression. |
4647
| `FixedMathSharp` | `WideTriangleRelations.TryGetContact(...)` | 20 | 100% line / 100% branch | Triangle-pair SAT retains two face normals, six in-plane edge normals, nine edge crosses, stable minimum-depth selection, and explicit separated exits in one allocation-free reducer. | A shared fixed-shape SAT iterator reduces branches while preserving axis order, tie ownership, and the ordinary triangle-pair benchmark. |
47-
| `FixedMathSharp` | `WideArithmetic.GetRoundedNonNegativeNormalizedDepth(...)` | 20 | 100% line / 100% branch | Exact clamping, tiny-axis bisection, constant-time ordinary-axis approximation, and nearest-even midpoint correction share one allocation-free final-depth conversion. | One constant-time exact divider covers tiny axes without weakening half-even results or regressing the ordinary normalized-depth path. |
48+
| `FixedMathSharp` | `WideArithmetic.GetRoundedNonNegativeNormalizedDepth(...)` | 18 | 100% line / 100% branch | Exact clamping, tiny-axis bisection, constant-time ordinary-axis approximation, and nearest-even midpoint correction share one allocation-free final-depth conversion. | One constant-time exact divider covers tiny axes without weakening half-even results or regressing the ordinary normalized-depth path. |
4849
| `FixedMathSharp` | `FixedBoundSphere.CreateFromPointSpan(ReadOnlySpan<Vector3d>)` | 18 | 100% line / 100% branch | Ritter-style bounding sphere construction has fixed selection and expansion branches; keeping it span-based preserves the allocation-free path. | More sphere construction modes are added, coverage drops, or the algorithm needs accuracy/performance tuning. |
4950
| `FixedMathSharp` | `FixedBoundSphere.CreateFromPointList(IReadOnlyList<Vector3d>)` | 18 | 100% line / 100% branch | Ritter-style bounding sphere construction has fixed selection and expansion branches; keeping it local preserves data flow and avoids extra passes. | More sphere construction modes are added, coverage drops, or the algorithm needs accuracy/performance tuning. |
5051
| `FixedMathSharp` | `FixedMath.Sin(Fixed64)` | 24 | 100% line / 100% branch | Trigonometric range reduction and complementary reduced-range polynomial selection are performance-sensitive and deterministic. Extraction would split a compact numeric routine. | Approximation guarantees change or benchmark evidence supports a simpler equally accurate path. |
@@ -136,6 +137,7 @@ behavior changes, or the implementation becomes harder to reason about.
136137
| `FixedMathSharp` | `Fixed64.CompareMagnitudeSquared(...)` | 12 | 100% line / 100% branch | Exact squared-magnitude ordering compares overflow, high, and low words directly without projecting either sum back into Q32.32. | A reusable fixed-width magnitude value provides the same lexicographic order without extra construction or cost. |
137138
| `FixedMathSharp` | `CoordinateConvention3d.ctor(Axis3d, Axis3d, Axis3d)` | 12 | 100% line / 100% branch | The public constructor validates three defined signed axes and rejects every duplicate absolute-axis pairing before storing an immutable basis. | Axis validation moves to a shared zero-overhead basis type with equally specific argument errors. |
138139
| `FixedMathSharp` | `FixedTransform.TrySetParentKeepingWorld(FixedTransform?)` | 12 | 100% line / 100% branch | Atomic reparenting validates ancestry, inverse, decomposition, and world recomposition before committing any local or parent state. | A shared atomic transform mutation result reduces branches without exposing partial state or weakening verification. |
140+
| `FixedMathSharp` | `Fixed64.TryGetSignedRawRatioCore(...)` | 11 | 100% line / 100% branch | Shared zero-denominator rejection, quotient-width validation, the measured single-limb specialization, general fixed-limb division, rounding, and signed materialization stay in one allocation-free deterministic boundary. | A simpler shared divider preserves zero, width, rounding, and signed-range policies with lower complexity and neutral or faster measured cost. |
139141

140142
## Fixed-Width Workspace Bounds
141143

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Single-Limb Raw-Ratio Performance Design
2+
3+
**Date:** 2026-08-01
4+
**Status:** Complete — specialization accepted
5+
**Repository:** `FixedMathSharp`
6+
7+
## Problem
8+
9+
The benchmark backlog records an incomplete performance signal for
10+
`Fixed64.TryGetSignedRawRatio(Signed576, Signed576, ...)` when the denominator
11+
has one active 64-bit limb. The original investigation was terminated without
12+
a completed timing sample and predates the overload's current `Signed192` and
13+
`Signed320` narrowing paths.
14+
15+
The shared divider still routes a one-limb denominator through equal-length
16+
multi-limb shift, compare, subtract, and midpoint work. That shape is a
17+
plausible bottleneck, but production specialization is not justified until a
18+
current isolated benchmark proves it.
19+
20+
## Goals
21+
22+
- Produce a stable current baseline for representative one-limb denominators.
23+
- Compare those rows with a multi-limb denominator control.
24+
- Specialize only if the measured cost is material.
25+
- Preserve the complete arbitrary signed wide-ratio contract: denominator and
26+
numerator signs, zero handling, round-half-to-even, signed raw range, and
27+
honest rejection of unrepresentable results.
28+
- Preserve deterministic behavior, zero allocation, `netstandard2.1`
29+
compatibility, and one portable implementation across target frameworks.
30+
- Retain a focused benchmark guard and close the backlog with measured evidence.
31+
32+
## Non-Goals
33+
34+
- Do not change any public API or rounding contract.
35+
- Do not assume the denominator is positive or that the quotient is
36+
representable.
37+
- Do not replace the general multi-limb divider.
38+
- Do not add `UInt128` or another target-specific implementation without a
39+
separate benchmark proving the portable `Divide128By64` helper is the
40+
remaining bottleneck.
41+
- Do not optimize unrelated wide-arithmetic paths.
42+
43+
## Benchmark Design
44+
45+
Add one dedicated `WideRawRatioBenchmarks` fixture with deterministic values
46+
covering:
47+
48+
1. A one-word numerator and one-word denominator.
49+
2. A two-word numerator and 32-bit denominator.
50+
3. A two-word numerator and 64-bit denominator.
51+
4. A one-word denominator whose quotient is immediately unrepresentable.
52+
5. A multi-word denominator control that remains on the general divider.
53+
54+
Every row calls the production `Signed576 / Signed576` contract and reports
55+
managed allocation. Capture the baseline and candidate with the same Release
56+
build, BenchmarkDotNet job, machine, filters, and artifact layout.
57+
58+
The specialization is accepted only when a stable out-of-process comparison
59+
shows at least a provisional 15% improvement in the affected representable
60+
rows, zero managed allocation, and no multi-limb control regression beyond 5%
61+
or ordinary run noise. If the baseline is already competitive, retain the
62+
benchmark and close the signal with a no-change decision.
63+
64+
## Conditional Production Design
65+
66+
If the evidence gate opens, specialize the shared
67+
`TryGetSignedRawRatioCore(...)` boundary when the active denominator length is
68+
one. The numerator can contain at most two active limbs when its quotient fits
69+
in 64 bits; larger magnitudes are rejected before division. Reuse the existing
70+
portable `Divide128By64(...)` helper, compare the remainder with
71+
`denominator - remainder` for overflow-free half-even classification, and
72+
delegate final rounding, signed-range validation, and materialization to the
73+
existing `TryCreateRawRatioResult(...)` owner.
74+
75+
This location serves `Signed576`, `Signed704`, `Signed832`, and span-backed
76+
callers without duplicating representation mechanics in an overload-specific
77+
fast path. Multi-limb denominators continue through the current fixed-limb
78+
divider unchanged.
79+
80+
## Correctness And Test Design
81+
82+
The optimization is behavior-preserving, so its RED evidence is the completed
83+
benchmark showing the current path misses the approved performance gate rather
84+
than an artificial unit-test failure. Existing wide-ratio tests remain the
85+
contract baseline. Before production changes, extend the `BigInteger` oracle
86+
matrix only where needed to pin:
87+
88+
- positive and negative numerator/denominator combinations;
89+
- below-half, exact even/odd midpoint, and above-half rounding;
90+
- one- and two-limb numerators;
91+
- `long.MaxValue` and `long.MinValue` materialization boundaries;
92+
- quotient overflow before rounding and overflow caused by rounding; and
93+
- equivalence between single-limb and general denominator results.
94+
95+
No test-only production hook, path counter, reflection check, or benchmark
96+
timing assertion belongs in the unit suite.
97+
98+
## Verification And Closure
99+
100+
- Re-run the exact baseline benchmark after each candidate change.
101+
- Run focused wide-arithmetic tests, then complete `Release` and `ReleaseLean`
102+
suites.
103+
- Preserve 100% reachable line, branch, and method coverage.
104+
- Build standard and Lean packages for both target frameworks with zero
105+
warnings.
106+
- Re-run downstream Gravitas gates only if the internal ABI or a consumed path
107+
changes materially.
108+
- Request independent correctness and performance review before closure.
109+
- Move the backlog entry to Closed Signals with the baseline, final evidence,
110+
and either the accepted specialization or explicit no-change decision.
111+
- Leave all changes unstaged and uncommitted for repository-owner review.
112+
113+
## Final Outcome
114+
115+
The shared one-limb-denominator specialization was accepted. Canonical
116+
BenchmarkDotNet `DefaultJob` artifacts compare base `28aef44` with the final
117+
candidate under the same Release/net8.0 environment:
118+
119+
| Row | Baseline mean | Candidate mean | Mean delta | Baseline median | Candidate median | Median delta | Allocated |
120+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
121+
| One-word numerator/denominator | 160.759 ns | 51.770 ns | -67.80% | 160.561 ns | 51.676 ns | -67.82% | 0 B -> 0 B |
122+
| Two-word numerator / 32-bit denominator | 279.194 ns | 61.077 ns | -78.12% | 277.527 ns | 60.916 ns | -78.05% | 0 B -> 0 B |
123+
| Two-word numerator / 64-bit denominator | 376.112 ns | 131.217 ns | -65.11% | 374.211 ns | 131.199 ns | -64.94% | 0 B -> 0 B |
124+
| Unrepresentable quotient | 42.586 ns | 43.207 ns | +1.46% | 42.568 ns | 43.174 ns | +1.43% | 0 B -> 0 B |
125+
| Multi-word denominator control | 73.190 ns | 72.257 ns | -1.27% | 72.626 ns | 71.670 ns | -1.32% | 0 B -> 0 B |
126+
127+
All three affected representable rows exceed the 15% improvement gate on mean
128+
and median. The multi-word control stays within the 5% regression gate by
129+
improving 1.27% by mean and 1.32% by median. Every row remains allocation-free,
130+
the public/general-divider contracts are unchanged, and final Release,
131+
ReleaseLean, coverage, package, downstream, and independent-review gates pass.
132+
Canonical artifacts are preserved under
133+
`artifacts/benchmarks/2026-08-02-single-limb-raw-ratio-canonical-baseline/` and
134+
`artifacts/benchmarks/2026-08-02-single-limb-raw-ratio-canonical-candidate/`.

docs/feature-work/benchmark-signal-hardening-backlog.md

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,45 @@ dotnet build tests/FixedMathSharp.Benchmarks/FixedMathSharp.Benchmarks.csproj -c
3737

3838
## Active Signals
3939

40-
| Signal | Status | Priority | Tracking |
41-
| ---------------------------- | ------- | -------- | -------- |
42-
| Single-limb-denominator path | Holding | Low | N/A |
43-
44-
### Signal: Single-limb-denominator path
45-
46-
**Discovered:** 2026-07-19
47-
**Source:** repeated `Fixed64.TryGetSignedRawRatio(Signed576, Signed576, ...)`
48-
benchmark runs
49-
**Status:** Observed; terminated run, no completed timing sample
50-
51-
- Benchmark any proposed single-limb-denominator path for the general
52-
`Fixed64.TryGetSignedRawRatio(Signed576, Signed576, ...)` contract before
53-
replacing its fixed-limb divider. Exact coordinate interpolation now owns a
54-
narrower `Signed320 / Signed192` path whose positive single-word denominator
55-
and representable quotient are construction invariants; it reduced the
56-
existing segment reconstruction benchmark from roughly 514/759 nanoseconds to
57-
about 121/118 nanoseconds at unit/100,000 scale. Do not generalize those
58-
invariants to arbitrary signed 576-bit callers without separate evidence.
40+
| Signal | Status | Priority | Tracking |
41+
| ------ | ------ | -------- | -------- |
42+
| none | | | |
5943

6044
## Closed Signals
6145

6246
| Signal | Status | Closed | Resolution |
6347
| ------ | ------ | ------ | ---------- |
64-
| none | | | |
48+
| Single-limb-denominator path | Accepted | 2026-08-02 | Shared portable specialization retained; canonical affected rows are 65.11%-78.12% faster by mean, the control improves 1.27%, and every row remains 0 B/op. |
49+
50+
### Closed: Single-limb-denominator path
51+
52+
Canonical BenchmarkDotNet `DefaultJob` comparison from base `28aef44` to the
53+
accepted candidate:
54+
55+
| Row | Baseline mean | Candidate mean | Delta | Allocated |
56+
| --- | ---: | ---: | ---: | ---: |
57+
| One-word numerator/denominator | 160.759 ns | 51.770 ns | -67.80% | 0 B -> 0 B |
58+
| Two-word numerator / 32-bit denominator | 279.194 ns | 61.077 ns | -78.12% | 0 B -> 0 B |
59+
| Two-word numerator / 64-bit denominator | 376.112 ns | 131.217 ns | -65.11% | 0 B -> 0 B |
60+
| Unrepresentable quotient | 42.586 ns | 43.207 ns | +1.46% | 0 B -> 0 B |
61+
| Multi-word denominator control | 73.190 ns | 72.257 ns | -1.27% | 0 B -> 0 B |
62+
63+
Reproduction commands, run from the matching baseline and candidate worktrees;
64+
the baseline is detached at `28aef44` with the final benchmark fixture copied
65+
unchanged before the build:
66+
67+
```powershell
68+
dotnet build tests/FixedMathSharp.Benchmarks/FixedMathSharp.Benchmarks.csproj -c Release -f net8.0 --nologo
69+
dotnet tests/FixedMathSharp.Benchmarks/bin/Release/net8.0/FixedMathSharp.Benchmarks.dll wide-raw-ratio --exporters json --artifacts artifacts/benchmarks/2026-08-02-single-limb-raw-ratio-canonical-baseline
70+
dotnet tests/FixedMathSharp.Benchmarks/bin/Release/net8.0/FixedMathSharp.Benchmarks.dll wide-raw-ratio --exporters json --artifacts artifacts/benchmarks/2026-08-02-single-limb-raw-ratio-canonical-candidate
71+
```
72+
73+
Artifacts are preserved under the two command paths above. FixedMathSharp
74+
passed Release 2,652/2,652, ReleaseLean 2,631/2,631, warning-free standard and
75+
Lean package builds, and 100% coverage at 53,003/53,003 lines,
76+
8,768/8,768 branches, and 3,411/3,411 methods. Gravitas passed Release
77+
3,925/3,925 and ReleaseLean 3,870/3,870 through the existing local links after
78+
version alignment and a fresh isolated restore; no Gravitas file changed. The
79+
independent pre-closure review's canonical-evidence finding was resolved with
80+
the matched DefaultJob artifacts, and follow-up review left no open Critical or
81+
Important findings.

0 commit comments

Comments
 (0)