diff --git a/MEMORY.md b/MEMORY.md index 96aab47..03b3771 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -20,20 +20,20 @@ Last updated: 2026-09-05 - Replacement source branches, their local worktrees, and the seven superseded Repo Assist source branches were deleted after merge/closure verification. -## Retained work +## Completed multiplication inlining experiment -- PR 223 (https://github.com/NichUK/FixedPointNano/pull/223) remains - intentionally open as a draft from - `repo-assist/perf-multiply-inline-2026-07-12-d0c46fd9cc0e034f` into `main`. -- Its exact head is `83b673f452ed33e4d0d4653fc6aebb8becf5bc7b`. GitHub reports - it is mergeable but unstable: the bot auto-approval workflow fails because the - GitHub Actions author cannot approve its own PR; this is not a code/test failure. -- It has no Copilot review or activity. Persistent monitoring is unavailable - outside an active task; resume from this record before taking action. -- Do not adopt its multiplication inlining hint until a current `develop` - candidate has controlled baseline-versus-candidate BenchmarkDotNet evidence - for multiplication and representative loops, with numerical equivalence and - generated-code/code-size inspection. The experiment and decision thresholds are - specified in `docs/performance/multiplication-inlining-test-plan.md`. Next action - is to implement that fixture and gather the required evidence or close the draft - deliberately; do not merge its stale `main`-based branch. +- Baseline `c4263974dbab1c3692ee9f01b46fc5176e16b197` and one-line candidate + `48ff2f09673c2c190099d192c7f289060080e62c` passed 1,560 tests and produced + byte-identical deterministic corpus checksums. +- Five ABBA blocks completed all 20 process invocations. Every workload's paired + 95% confidence interval crossed parity; none reached the required 8/10 direction + count. The aggregate candidate/baseline ratio was 1.0224 with a 95% interval of + 0.9803-1.0640 and the candidate faster in 4/10 pairs. +- The attribute was rejected because it demonstrated no repeatable consumer + benefit. It is absent from the delivery branch. The fixture and concise evidence + are retained under `docs/performance/results/2026-09-05-multiplication-inlining`; + uncommitted raw artifacts were retained outside the repository on the measurement + host. +- PR 223 was closed intentionally with the measured rationale. Its rejected remote + source branch was deleted after verifying that its only useful change was the + tested inlining attribute. diff --git a/README.md b/README.md index 781c27b..f2654d8 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,5 @@ are not evidence of a speedup. Rounding rewrites and additional inlining hints remain deferred until controlled before/after benchmarks demonstrate a benefit. The acceptance criteria and paired-run protocol for the multiplication hint are defined in [the multiplication inlining test plan](https://github.com/NichUK/FixedPointNano/blob/main/docs/performance/multiplication-inlining-test-plan.md). +The completed experiment found no repeatable benefit, so the hint was not adopted; +the [full result is retained on GitHub](https://github.com/NichUK/FixedPointNano/blob/main/docs/performance/results/2026-09-05-multiplication-inlining/REPORT.md). diff --git a/benchmarks/FixedPointNano.Benchmarks/FixedPointNanoMultiplyInliningBenchmarks.cs b/benchmarks/FixedPointNano.Benchmarks/FixedPointNanoMultiplyInliningBenchmarks.cs new file mode 100644 index 0000000..cbd2a1b --- /dev/null +++ b/benchmarks/FixedPointNano.Benchmarks/FixedPointNanoMultiplyInliningBenchmarks.cs @@ -0,0 +1,210 @@ +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using Fpn = Seerstone.FixedPointNano; + +namespace FixedPointNano.Benchmarks; + +[MemoryDiagnoser] +public class FixedPointNanoMultiplyInliningBenchmarks +{ + private const int BatchSize = 1024; + private const int PowExponent = 5; + private Fpn[] _amounts = []; + private Fpn[] _dependentFactors = []; + private Fpn[] _left = []; + private Fpn[] _notionalPrices = []; + private Fpn[] _notionalQuantities = []; + private Fpn[] _powValues = []; + private Fpn[] _right = []; + private Fpn[] _squareValues = []; + private Fpn _singleLeft; + private Fpn _singleRight; + + [GlobalSetup] + public void Setup() + { + _amounts = new Fpn[BatchSize]; + _dependentFactors = new Fpn[BatchSize]; + _left = new Fpn[BatchSize]; + _notionalPrices = new Fpn[BatchSize]; + _notionalQuantities = new Fpn[BatchSize]; + _powValues = new Fpn[BatchSize]; + _right = new Fpn[BatchSize]; + _squareValues = new Fpn[BatchSize]; + + for (var index = 0; index < BatchSize; index++) + { + var leftSign = (index & 1) == 0 ? 1L : -1L; + var rightSign = index % 3 == 0 ? -1L : 1L; + _left[index] = Fpn.FromRaw(leftSign * (750_000_001L + (index * 1_000_003L))); + _right[index] = Fpn.FromRaw(rightSign * (250_000_003L + (index * 7_919L))); + + _dependentFactors[index] = index % 2 == 0 + ? Fpn.FromRaw(1_000_100_003L) + : Fpn.FromRaw(999_899_997L); + + _notionalPrices[index] = Fpn.FromRaw(10_000_000_001L + (index * 10_000_019L)); + var quantitySign = index % 5 == 0 ? -1L : 1L; + _notionalQuantities[index] = Fpn.FromRaw(quantitySign * (1_000_000_007L + (index * 100_003L))); + + _amounts[index] = Fpn.FromRaw((index % 101) * 10_000_000L); + _squareValues[index] = Fpn.FromRaw(250_000_001L + (index * 10_000_019L)); + _powValues[index] = Fpn.FromRaw(900_000_001L + ((index % 201) * 1_000_003L)); + } + + _left[0] = Fpn.Zero; + _right[0] = Fpn.FromRaw(123_456_789L); + _left[1] = Fpn.FromRaw(1L); + _right[1] = Fpn.FromRaw(499_999_999L); + _left[2] = Fpn.FromRaw(1L); + _right[2] = Fpn.FromRaw(500_000_000L); + _left[3] = Fpn.FromRaw(1L); + _right[3] = Fpn.FromRaw(500_000_001L); + _left[4] = Fpn.FromRaw(3L); + _right[4] = Fpn.FromRaw(500_000_000L); + _left[5] = Fpn.FromRaw(-3L); + _right[5] = Fpn.FromRaw(500_000_000L); + _left[6] = Fpn.FromRaw(2_000_000_000_000L); + _right[6] = Fpn.FromRaw(3_000_000_000L); + _amounts[0] = Fpn.Zero; + _amounts[1] = Fpn.FromRaw(1L); + _amounts[2] = Fpn.FromRaw(499_999_999L); + _amounts[3] = Fpn.FromRaw(500_000_000L); + _amounts[4] = Fpn.FromRaw(500_000_001L); + _amounts[5] = Fpn.FromRaw(999_999_999L); + _amounts[6] = Fpn.One; + _squareValues[0] = Fpn.FromRaw(2_000_000_000_000L); + _squareValues[1] = Fpn.FromRaw(-2_000_000_000_000L); + + _singleLeft = _left[17]; + _singleRight = _right[17]; + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public Fpn SingleMultiply() + { + return _singleLeft * _singleRight; + } + + [Benchmark(OperationsPerInvoke = BatchSize)] + [MethodImpl(MethodImplOptions.NoInlining)] + public long IndependentMultiplyBatch() + { + var checksum = 0L; + for (var index = 0; index < BatchSize; index++) + { + checksum = unchecked(checksum + (_left[index] * _right[index]).RawValue); + } + + return checksum; + } + + [Benchmark(OperationsPerInvoke = BatchSize)] + [MethodImpl(MethodImplOptions.NoInlining)] + public long DependentMultiplyChain() + { + var current = Fpn.One; + for (var index = 0; index < BatchSize; index++) + { + current *= _dependentFactors[index]; + } + + return current.RawValue; + } + + [Benchmark(OperationsPerInvoke = BatchSize)] + [MethodImpl(MethodImplOptions.NoInlining)] + public long NotionalBatch() + { + var sum = Fpn.Zero; + for (var index = 0; index < BatchSize; index++) + { + sum += _notionalPrices[index] * _notionalQuantities[index]; + } + + return sum.RawValue; + } + + [Benchmark(OperationsPerInvoke = BatchSize)] + [MethodImpl(MethodImplOptions.NoInlining)] + public long LerpBatch() + { + var checksum = 0L; + for (var index = 0; index < BatchSize; index++) + { + checksum = unchecked(checksum + Fpn.Lerp(_left[index], _right[index], _amounts[index]).RawValue); + } + + return checksum; + } + + [Benchmark(OperationsPerInvoke = BatchSize)] + [MethodImpl(MethodImplOptions.NoInlining)] + public long SquareBatch() + { + var checksum = 0L; + for (var index = 0; index < BatchSize; index++) + { + checksum = unchecked(checksum + Fpn.Square(_squareValues[index]).RawValue); + } + + return checksum; + } + + [Benchmark(OperationsPerInvoke = BatchSize)] + [MethodImpl(MethodImplOptions.NoInlining)] + public long PowBatch() + { + var checksum = 0L; + for (var index = 0; index < BatchSize; index++) + { + checksum = unchecked(checksum + Fpn.Pow(_powValues[index], PowExponent).RawValue); + } + + return checksum; + } + + public IReadOnlyList> CaptureChecksums() + { + IReadOnlyList> checksums = + [ + new(nameof(IndependentMultiplyBatch), IndependentMultiplyBatch()), + new(nameof(DependentMultiplyChain), DependentMultiplyChain()), + new(nameof(NotionalBatch), NotionalBatch()), + new(nameof(LerpBatch), LerpBatch()), + new(nameof(SquareBatch), SquareBatch()), + new(nameof(PowBatch), PowBatch()), + ]; + + foreach (var checksum in checksums) + { + VerifyChecksum(checksum.Key, checksum.Value, GetExpectedChecksum(checksum.Key)); + } + + return checksums; + } + + private static long GetExpectedChecksum(string benchmark) + { + return benchmark switch + { + nameof(IndependentMultiplyBatch) => 6_000_323_985_234L, + nameof(DependentMultiplyChain) => 999_994_879L, + nameof(NotionalBatch) => 9_816_311_662_619L, + nameof(LerpBatch) => 46_212_822_455L, + nameof(SquareBatch) => 8_038_421_873_095_119L, + nameof(PowBatch) => 1_050_735_154_786L, + _ => throw new ArgumentOutOfRangeException(nameof(benchmark), benchmark, "Unknown benchmark."), + }; + } + + private static void VerifyChecksum(string benchmark, long actual, long expected) + { + if (actual != expected) + { + throw new InvalidOperationException( + $"The {benchmark} corpus checksum was {actual}, but {expected} was expected."); + } + } +} diff --git a/benchmarks/FixedPointNano.Benchmarks/Program.cs b/benchmarks/FixedPointNano.Benchmarks/Program.cs index 564eb9d..22c0196 100644 --- a/benchmarks/FixedPointNano.Benchmarks/Program.cs +++ b/benchmarks/FixedPointNano.Benchmarks/Program.cs @@ -1,3 +1,4 @@ +using System.Globalization; using BenchmarkDotNet.Running; namespace FixedPointNano.Benchmarks; @@ -6,6 +7,18 @@ internal static class Program { private static void Main(string[] args) { + if (args.Length == 1 && string.Equals(args[0], "--verify-inlining-corpus", StringComparison.Ordinal)) + { + var benchmarks = new FixedPointNanoMultiplyInliningBenchmarks(); + benchmarks.Setup(); + foreach (var checksum in benchmarks.CaptureChecksums()) + { + Console.WriteLine($"{checksum.Key}={checksum.Value.ToString(CultureInfo.InvariantCulture)}"); + } + + return; + } + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } } diff --git a/docs/performance/multiplication-inlining-test-plan.md b/docs/performance/multiplication-inlining-test-plan.md index 1eb2555..75760d5 100644 --- a/docs/performance/multiplication-inlining-test-plan.md +++ b/docs/performance/multiplication-inlining-test-plan.md @@ -1,5 +1,14 @@ # Multiplication inlining test plan +## Outcome + +The 2026-09-05 experiment did not support adding the attribute. Every workload's +paired 95% confidence interval crossed parity, no workload met the required +direction count, and the aggregate candidate-to-baseline point estimate was +`1.0224` (2.24% slower). The stale proposal was closed without adoption. See the +[experiment report](results/2026-09-05-multiplication-inlining/REPORT.md) and its +[machine-readable summary](results/2026-09-05-multiplication-inlining/summary.json). + ## Decision This experiment decides whether to add diff --git a/docs/performance/results/2026-09-05-multiplication-inlining/REPORT.md b/docs/performance/results/2026-09-05-multiplication-inlining/REPORT.md new file mode 100644 index 0000000..1a1bfed --- /dev/null +++ b/docs/performance/results/2026-09-05-multiplication-inlining/REPORT.md @@ -0,0 +1,55 @@ +# Multiplication inlining experiment + +- Baseline: `c4263974dbab1c3692ee9f01b46fc5176e16b197` +- Candidate: `48ff2f09673c2c190099d192c7f289060080e62c` +- Runtime: .NET 10.0.9; SDK 10.0.301; BenchmarkDotNet 0.14.0 +- Host: Windows 11 Pro build 26200; Intel Core i7-1265U, 10 cores / 12 logical processors +- Protocol: five ABBA blocks, 20 separate processes, 10 warmups, 15 measurements, 250 ms target iteration + +## Recommendation + +**Reject the candidate and do not merge the inlining attribute.** The mandatory adoption gates do not pass. The formal timing classification is inconclusive because every 95% paired confidence interval crosses 1.00, while the aggregate point estimate is 2.24% slower and only 4/10 aggregate pairs favor the candidate. A repeat on dedicated lab hardware is the valid next action only if further evidence is desired. + +## Results + +| Workload | Baseline geometric mean | Candidate geometric mean | Candidate / baseline | Paired bootstrap 95% CI | Candidate faster | Gate | +|---|---:|---:|---:|---:|---:|---| +| IndependentMultiplyBatch | 9.209 ns | 9.779 ns | 1.0619 | 0.9672-1.1750 | 4/10 | Fail | +| DependentMultiplyChain | 5.814 ns | 5.726 ns | 0.9848 | 0.9123-1.0552 | 6/10 | Fail | +| NotionalBatch | 7.955 ns | 8.004 ns | 1.0061 | 0.9153-1.1003 | 5/10 | Fail | +| LerpBatch | 11.025 ns | 11.734 ns | 1.0644 | 0.8919-1.2633 | 5/10 | Fail | +| SquareBatch | 7.376 ns | 7.020 ns | 0.9518 | 0.8217-1.1031 | 6/10 | Fail | +| PowBatch | 42.365 ns | 45.384 ns | 1.0713 | 0.9319-1.2395 | 6/10 | Fail | +| **Aggregate** | - | - | **1.0224** | **0.9803-1.0640** | **4/10** | **Fail** | + +All benchmarked workloads allocated 0 B in both variants. Both variants passed all 1,560 tests, the focused 1,356-case math comparison suite, and byte-identical corpus checksum verification. + +## Generated code + +Under runtime defaults, IndependentMultiplyBatch, NotionalBatch, and PowBatch were instruction-equivalent after address normalization. DependentMultiplyChain, LerpBatch, and SquareBatch differed, but all six hot callers had exactly identical native code sizes between variants (23,287 B aggregate, 0% growth). With tiered compilation disabled, all six hot callers were instruction-equivalent and size-equivalent. The diagnostic SingleMultiply changed and is excluded from the adoption gate. + +## Gate decision + +- Relevant generated-code difference: pass under defaults for three consumers. +- IndependentMultiplyBatch improves at least 2% with CI below 1.00: fail; point estimate is 6.19% slower. +- DependentMultiplyChain improves at least 2% with CI below 1.00: fail; point estimate improves 1.52%, CI crosses 1.00. +- Representative workload improves at least 1% with CI below 1.00: fail; every CI crosses 1.00. +- At least 8/10 pairs favor the candidate: fail for every workload. +- No representative regression over 1%: fail by point estimate for IndependentMultiplyBatch, LerpBatch, and PowBatch. +- Allocation and code-size budgets: pass. + +## Quality and evidence + +Fifteen of 20 default-runtime invocations emitted a multimodal-distribution warning, confirming meaningful host noise. No command failed and all raw reports were retained; the wide confidence intervals prevent claiming a speedup. + +The exact tested fixture validated its checksums during `GlobalSetup`, invoking +each workload once before BenchmarkDotNet's ten warmup iterations. The retained +fixture performs validation only through `--verify-inlining-corpus` so future +measurements begin with corpus initialization alone. + +- Machine/runtime: `dotnet-info.txt`, `os.txt`, `cpu.txt`, `power-plan.txt` +- Correctness: `baseline-tests.txt`, `candidate-tests.txt`, `baseline-checksums.txt`, `candidate-checksums.txt`, `checksum-comparison.txt` +- Assembly: `assembly-comparison.txt`, `disasm-default-*`, `disasm-tieredoff-*` +- Timing: `timing-default/`, `paired-ratios.csv`, `paired-analysis.txt` +- Secondary sensitivity: `tieredoff-sensitivity.txt` +- Machine-readable result: `summary.json` diff --git a/docs/performance/results/2026-09-05-multiplication-inlining/commands.txt b/docs/performance/results/2026-09-05-multiplication-inlining/commands.txt new file mode 100644 index 0000000..c731a0e --- /dev/null +++ b/docs/performance/results/2026-09-05-multiplication-inlining/commands.txt @@ -0,0 +1,19 @@ +Baseline and candidate experiment commands +Baseline: c4263974dbab1c3692ee9f01b46fc5176e16b197 +Candidate: 48ff2f09673c2c190099d192c7f289060080e62c +Validation (run once in each exact-commit worktree): +dotnet build FixedPointNano.slnx -c Release --nologo +dotnet test FixedPointNano.slnx -c Release --no-build --nologo +dotnet run --project benchmarks\FixedPointNano.Benchmarks -c Release --no-build -- --verify-inlining-corpus +dotnet test tests\FixedPointNano.Tests\FixedPointNano.Tests.csproj -c Release --no-build --nologo --filter FullyQualifiedName~FixedPointNanoMathComparisonTests + +Default disassembly (run once in each exact-commit worktree): +dotnet run --project benchmarks\FixedPointNano.Benchmarks -c Release --no-build -- --filter *FixedPointNanoMultiplyInliningBenchmarks* --disasm --launchCount 1 --warmupCount 10 --iterationCount 15 --iterationTime 250 + +Tiered-compilation-disabled disassembly/sensitivity (run once in each exact-commit worktree): +$env:DOTNET_TieredCompilation = "0" +dotnet run --project benchmarks\FixedPointNano.Benchmarks -c Release --no-build -- --filter *FixedPointNanoMultiplyInliningBenchmarks* --disasm --launchCount 1 --warmupCount 10 --iterationCount 15 --iterationTime 250 +Remove-Item Env:DOTNET_TieredCompilation + +Default timing (five ABBA blocks, 20 separate invocations): +dotnet run --project benchmarks\FixedPointNano.Benchmarks -c Release --no-build -- --filter *Batch* *DependentMultiplyChain* --launchCount 1 --warmupCount 10 --iterationCount 15 --iterationTime 250 diff --git a/docs/performance/results/2026-09-05-multiplication-inlining/paired-ratios.csv b/docs/performance/results/2026-09-05-multiplication-inlining/paired-ratios.csv new file mode 100644 index 0000000..0a23b17 --- /dev/null +++ b/docs/performance/results/2026-09-05-multiplication-inlining/paired-ratios.csv @@ -0,0 +1,61 @@ +Pair,Method,BaselineMeanNs,CandidateMeanNs,Ratio,BaselineAllocated,CandidateAllocated +block1-first,IndependentMultiplyBatch,8.385,12.057,1.4379248658318426,0 B,0 B +block1-second,IndependentMultiplyBatch,8.322,7.564,0.9089161259312666,0 B,0 B +block2-first,IndependentMultiplyBatch,7.622,7.562,0.9921280503804776,0 B,0 B +block2-second,IndependentMultiplyBatch,8.746,11.691,1.3367253601646467,0 B,0 B +block3-first,IndependentMultiplyBatch,9.728,10.001,1.028063322368421,0 B,0 B +block3-second,IndependentMultiplyBatch,7.564,8.529,1.1275780010576415,0 B,0 B +block4-first,IndependentMultiplyBatch,9.673,8.112,0.8386229711568283,0 B,0 B +block4-second,IndependentMultiplyBatch,12.845,13.301,1.03550019462826,0 B,0 B +block5-first,IndependentMultiplyBatch,10.365,9.894,0.9545586107091172,0 B,0 B +block5-second,IndependentMultiplyBatch,9.949,10.889,1.0944818574731128,0 B,0 B +block1-first,DependentMultiplyChain,5.049,4.995,0.9893048128342246,0 B,0 B +block1-second,DependentMultiplyChain,5.052,5.075,1.0045526524148853,0 B,0 B +block2-first,DependentMultiplyChain,5.012,4.685,0.934756584197925,0 B,0 B +block2-second,DependentMultiplyChain,6.403,5.977,0.9334686865531783,0 B,0 B +block3-first,DependentMultiplyChain,6.384,4.828,0.756265664160401,0 B,0 B +block3-second,DependentMultiplyChain,4.904,4.859,0.9908238172920065,0 B,0 B +block4-first,DependentMultiplyChain,5.966,7.054,1.1823667448876969,0 B,0 B +block4-second,DependentMultiplyChain,7.665,8.486,1.1071102413568168,0 B,0 B +block5-first,DependentMultiplyChain,6.211,5.753,0.9262598615359845,0 B,0 B +block5-second,DependentMultiplyChain,6.065,6.607,1.0893652102225886,0 B,0 B +block1-first,NotionalBatch,6.037,7.473,1.2378664899784662,0 B,0 B +block1-second,NotionalBatch,6.414,6.581,1.026036794512005,0 B,0 B +block2-first,NotionalBatch,6.547,6.34,0.9683824652512601,0 B,0 B +block2-second,NotionalBatch,9.669,7.271,0.7519908987485778,0 B,0 B +block3-first,NotionalBatch,7.768,8.936,1.1503604531410916,0 B,0 B +block3-second,NotionalBatch,6.851,6.385,0.931980732739746,0 B,0 B +block4-first,NotionalBatch,7.559,9.05,1.197248313268951,0 B,0 B +block4-second,NotionalBatch,11.474,12.697,1.1065888094823078,0 B,0 B +block5-first,NotionalBatch,9.233,7.908,0.8564930141882379,0 B,0 B +block5-second,NotionalBatch,9.716,9.178,0.9446274186908195,0 B,0 B +block1-first,LerpBatch,9.099,8.632,0.948675678646005,0 B,0 B +block1-second,LerpBatch,9.521,13.146,1.380737317508665,0 B,0 B +block2-first,LerpBatch,12.351,7.926,0.6417294146222977,0 B,0 B +block2-second,LerpBatch,12.545,9.541,0.7605420486249502,0 B,0 B +block3-first,LerpBatch,10.979,15.102,1.3755351124874762,0 B,0 B +block3-second,LerpBatch,9.333,12.975,1.3902282224365154,0 B,0 B +block4-first,LerpBatch,9.287,14.935,1.6081619468073651,0 B,0 B +block4-second,LerpBatch,14.91,14.217,0.9535211267605634,0 B,0 B +block5-first,LerpBatch,11.914,10.979,0.9215208997817693,0 B,0 B +block5-second,LerpBatch,11.691,12.627,1.0800615858352578,0 B,0 B +block1-first,SquareBatch,6.287,6.588,1.0478765707014475,0 B,0 B +block1-second,SquareBatch,5.627,5.611,0.9971565665541141,0 B,0 B +block2-first,SquareBatch,6.201,5.331,0.8597000483792938,0 B,0 B +block2-second,SquareBatch,6.309,9.467,1.5005547630369314,0 B,0 B +block3-first,SquareBatch,6.821,5.719,0.8384401114206129,0 B,0 B +block3-second,SquareBatch,6.339,7.581,1.195929957406531,0 B,0 B +block4-first,SquareBatch,9.113,5.825,0.6391967518929003,0 B,0 B +block4-second,SquareBatch,10.345,10.888,1.052489125181247,0 B,0 B +block5-first,SquareBatch,7.33,7.114,0.9705320600272851,0 B,0 B +block5-second,SquareBatch,11.526,7.967,0.6912198507721673,0 B,0 B +block1-first,PowBatch,40.376,35.944,0.8902318208836933,0 B,0 B +block1-second,PowBatch,33.627,44.661,1.3281291819073957,0 B,0 B +block2-first,PowBatch,37.62,51.064,1.3573631047315258,0 B,0 B +block2-second,PowBatch,36.056,54.041,1.4988074106944753,0 B,0 B +block3-first,PowBatch,41.285,40.659,0.9848371079084414,0 B,0 B +block3-second,PowBatch,37.838,34.339,0.9075268248850361,0 B,0 B +block4-first,PowBatch,55.824,43.695,0.7827278589853827,0 B,0 B +block4-second,PowBatch,46.132,67.14,1.4553888840717941,0 B,0 B +block5-first,PowBatch,46.53,43.813,0.9416075650118204,0 B,0 B +block5-second,PowBatch,54.029,46.633,0.8631105517407318,0 B,0 B diff --git a/docs/performance/results/2026-09-05-multiplication-inlining/summary.json b/docs/performance/results/2026-09-05-multiplication-inlining/summary.json new file mode 100644 index 0000000..2fc44bf --- /dev/null +++ b/docs/performance/results/2026-09-05-multiplication-inlining/summary.json @@ -0,0 +1,167 @@ +{ + "experimentDate": "2026-09-05", + "runtime": ".NET 10.0.9", + "sdk": "10.0.301", + "benchmarkDotNet": "0.14.0", + "baselineSha": "c4263974dbab1c3692ee9f01b46fc5176e16b197", + "candidateSha": "48ff2f09673c2c190099d192c7f289060080e62c", + "candidateChange": "MethodImplOptions.AggressiveInlining on operator * only", + "protocol": { + "blocks": 5, + "order": "ABBA", + "invocations": 20, + "launchCount": 1, + "warmupCount": 10, + "iterationCount": 15, + "iterationTimeMs": 250, + "bootstrapSeed": 20260905, + "bootstrapResamples": 100000 + }, + "functional": { + "baselineTestsPassed": 1560, + "candidateTestsPassed": 1560, + "mathComparisonTestsPassedEach": 1356, + "checksumByteEqual": true + }, + "defaultRuntime": { + "methods": { + "IndependentMultiplyBatch": { + "geometricMeanRatio": 1.0618824627934038, + "bootstrap95Low": 0.9671941758470088, + "bootstrap95High": 1.1750243577052693, + "candidateFasterCount": 4, + "pairCount": 10, + "baselineGeometricMeanNs": 9.208795552489935, + "candidateGeometricMeanNs": 9.778658500638958, + "allocationsEqualZero": true + }, + "DependentMultiplyChain": { + "geometricMeanRatio": 0.9848319634588476, + "bootstrap95Low": 0.9123046990728837, + "bootstrap95High": 1.0552134808916487, + "candidateFasterCount": 6, + "pairCount": 10, + "baselineGeometricMeanNs": 5.814192892963568, + "candidateGeometricMeanNs": 5.72600300270579, + "allocationsEqualZero": true + }, + "NotionalBatch": { + "geometricMeanRatio": 1.006118651479734, + "bootstrap95Low": 0.9153269058208361, + "bootstrap95High": 1.1003311497577453, + "candidateFasterCount": 5, + "pairCount": 10, + "baselineGeometricMeanNs": 7.955082261949157, + "candidateGeometricMeanNs": 8.00375663780264, + "allocationsEqualZero": true + }, + "LerpBatch": { + "geometricMeanRatio": 1.0643575858882548, + "bootstrap95Low": 0.8918811583024772, + "bootstrap95High": 1.2633022528716789, + "candidateFasterCount": 5, + "pairCount": 10, + "baselineGeometricMeanNs": 11.024770087335252, + "candidateGeometricMeanNs": 11.734297675129193, + "allocationsEqualZero": true + }, + "SquareBatch": { + "geometricMeanRatio": 0.9517718025382297, + "bootstrap95Low": 0.821679594689229, + "bootstrap95High": 1.1031275703188985, + "candidateFasterCount": 6, + "pairCount": 10, + "baselineGeometricMeanNs": 7.375960614968228, + "candidateGeometricMeanNs": 7.0202313299593, + "allocationsEqualZero": true + }, + "PowBatch": { + "geometricMeanRatio": 1.0712566700407842, + "bootstrap95Low": 0.9318531952400574, + "bootstrap95High": 1.239482384556569, + "candidateFasterCount": 6, + "pairCount": 10, + "baselineGeometricMeanNs": 42.365383600496074, + "candidateGeometricMeanNs": 45.3841997608679, + "allocationsEqualZero": true + }, + "AggregateSix": { + "geometricMeanRatio": 1.022351612114452, + "bootstrap95Low": 0.9803361017006766, + "bootstrap95High": 1.0640444258456931, + "candidateFasterCount": 4, + "pairCount": 10 + } + }, + "multimodalWarningRuns": 15, + "commandFailures": 0 + }, + "codeSizeBytes": { + "default": { + "baseline": { + "IndependentMultiplyBatch": 3680, + "DependentMultiplyChain": 3926, + "NotionalBatch": 3914, + "LerpBatch": 3484, + "SquareBatch": 3955, + "PowBatch": 4328 + }, + "candidate": { + "IndependentMultiplyBatch": 3680, + "DependentMultiplyChain": 3926, + "NotionalBatch": 3914, + "LerpBatch": 3484, + "SquareBatch": 3955, + "PowBatch": 4328 + } + }, + "tieredCompilationOff": { + "baseline": { + "IndependentMultiplyBatch": 863, + "DependentMultiplyChain": 840, + "NotionalBatch": 871, + "LerpBatch": 903, + "SquareBatch": 831, + "PowBatch": 489 + }, + "candidate": { + "IndependentMultiplyBatch": 863, + "DependentMultiplyChain": 840, + "NotionalBatch": 871, + "LerpBatch": 903, + "SquareBatch": 831, + "PowBatch": 489 + } + } + }, + "assemblyEquivalentAfterAddressNormalization": { + "default": { + "IndependentMultiplyBatch": true, + "DependentMultiplyChain": false, + "NotionalBatch": true, + "LerpBatch": false, + "SquareBatch": false, + "PowBatch": true + }, + "tieredCompilationOff": { + "IndependentMultiplyBatch": true, + "DependentMultiplyChain": true, + "NotionalBatch": true, + "LerpBatch": true, + "SquareBatch": true, + "PowBatch": true + } + }, + "decision": { + "adoptionGatePassed": false, + "statisticalClassification": "inconclusive", + "recommendation": "reject", + "action": "Do not implement the attribute; close stale PR 223 intentionally." + }, + "limitations": [ + "15 of 20 timing invocations emitted a BenchmarkDotNet multimodal-distribution warning.", + "All workload confidence intervals cross 1.00.", + "The machine is a developer laptop rather than an isolated performance lab.", + "The exact tested fixture invoked each workload once for checksum validation during GlobalSetup before ten warmup iterations; the retained fixture moves that validation to the explicit checksum command." + ] +}