Skip to content

fix: repair reference resolution, bundling, and comparison regressions - #686

Merged
daveshanley merged 33 commits into
mainfrom
codex/october-issue-repairs
Nov 1, 2026
Merged

daveshanley merged 33 commits into
mainfrom
codex/october-issue-repairs

Conversation

@daveshanley

@daveshanley daveshanley commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Reference-heavy documents can lose schema fields, produce incomplete bundles, or panic. This repairs the remaining October reports and the additional defects found during review.

  • Resolve Swagger 2 path items and multi-file schema aliases in the owning document.
  • Preserve required-reference paths and circular journeys; ignore references inside example payloads.
  • Preserve percent-encoded path items, shared alias targets, and discriminator aliases during composition.
  • Integrate fix(index): resolve component references through YAML aliases and merges #673 with bounded alias traversal, shared merge-source deduplication, and visible errors for incomplete indexes.
  • Integrate fix: compare the schema behind a changed $ref #668 with bounded schema-pair traversal and cached union candidate scores. Each candidate retains independent comparison state.
  • Parse decimal count constraints exactly, count allOf breaking changes once, and preserve untyped SDK error-response precedence.

The original contributor commits from #668 and #673 are retained. This PR replaces those two proposals after their review findings are fixed. PRs #669, #675, #677, and #685 were merged separately and are now included in this branch's base.

Fixes #663.
Fixes #672.
Fixes #678.
Fixes #679.
Fixes #680.
Fixes #681.
Fixes #682.
Fixes #683.
Fixes #684.

Repeated resolved schema pairs are reported once within a comparison; local reference changes remain visible. Separate public comparisons do not share this state. Excessive YAML alias or inherited-merge expansion returns a build error instead of an incomplete model.

Review repairs:

  • Distinguish schema and component names from example keywords, including external fragments.
  • Stop recursive union scoring without relying on circular-reference metadata; cache completed scores only.
  • Keep transient resolver paths and extraction state on the stack; deep paths still grow normally. Combine merge-key and schema-ID scans, and use an ASCII lookup for JSONPath name validation.
  • Track ancestry only for aliases and merged mappings. Add AllowExcessiveAliases to both configuration types for trusted documents; cycle guards remain enabled.
  • Return a build error for reference-only path cycles in both Swagger 2 and OpenAPI 3.
  • Compare siblings with at most four workers, private traversal state, deterministic replay, and per-worker shared-pair history. Skip property-check allocations when both fields are absent.
  • Isolate cached external-file indexes and YAML nodes per document owner. Parse once under concurrent opens, preserve cyclic lookup node identity, and retain lazy indexes during batch registration. This repairs the race exposed in unmodified Vacuum.

Release notes: TotalBreakingChanges() counts each allOf change once. Retargeted references now report changes inside the target as well as the reference change; repeated resolved pairs contribute details once per public comparison. Empty schema-change maps may be nil; JSON/YAML output and change counts are unchanged.

Validation at 9d96a62c: all 35 local library test packages pass, covering all 31,437 statements with zero uncovered blocks. Index, bundler, and root document race tests pass. Hosted CI passes Linux coverage, the full race suite, and Windows tests; CodeQL also passes. Codecov confirms 100% project and 100% patch coverage. Independent correctness and code-quality reviews are complete, and their confirmed findings are fixed.

Downstream compatibility is verified against unmodified Vacuum main 1fd76af2 and openapi-changes main 1750dae3, using isolated module replacements. Vacuum passes all 36 test packages, race checks for motor and functions/openapi, and 20 repeated race-enabled runs of the original LazyFS failure. openapi-changes passes all seven test packages and race checks for cmd, git, and internal/changecounts.

CLI checks cover all six demo/speed-test specs. Vacuum produces the same 39,248 findings as main; --original filtering matches exactly for Plaid (257) and Stripe (889). Circular-reference journeys reflect the corrected cycle paths. openapi-changes preserves change records apart from existing path-selection variability; Plaid's breaking summary removes duplicated allOf counts. Canonical JSON, summary, Markdown, and HTML workflows pass.

Final-head performance and downstream report: six balanced pairs compare 9d96a62c with main c6910f28 on the requested corpus. Geometric mean median time changes are indexing −4.02%, building −1.31%, rendering −0.33%, comparison −2.42%, and bundling −0.81%. Stripe rendering is 1.02% slower (191.361 → 193.303 ms; p=0.394). Timing outliers remain in the statistics, so the aggregate improvements do not establish uniform gains or tight performance parity. All 12 accepted runs pass and comparison change counts match. This report supersedes performance measurements for earlier PR heads.

Risk: high because reference resolution and comparison affect several library entry points. Tests cover source context, repeated calls, cycles, alias reuse, shared filesystem ownership, and generated SDK behavior. No deployment or migration is required.

@codecov

codecov Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (c6910f2) to head (9d96a62).

Additional details and impacted files
@@            Coverage Diff             @@
##              main      #686    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          310       313     +3     
  Lines        39401     39557   +156     
==========================================
+ Hits         39401     39557   +156     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@daveshanley
daveshanley marked this pull request as ready for review October 31, 2026 15:21

@daveshanley daveshanley left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nine issues closed, two contributors' work rescued, and a schema called example that kills the process. Le sigh.

The bones of this PR are good. Merged with latest main, index, what-changed, bundler, datamodel, utils and root all pass, and Codecov is green at 4bf027e9. Joseph's and Luca's work holds up. But it can't ship yet.

Agent working this PR: this is your worklist. Do it in order. Each inline comment has the exact change and the test I want.

The worklist

  1. Blocker. Stop treating a schema named example as example data (index/resolver_relatives.go). Circular references go undetected.
  2. Blocker. Guard score against recursion (what-changed/model/schema_comparison.go). Today it can die with a fatal stack overflow, and recover() can't save us.
  3. Blocker, performance. Gate the ancestry map in extractRefs (index/extract_refs.go). Tested fix, index cost drops to 1-4%.
  4. Blocker, performance. Bring concurrency back to compare (what-changed/model/schema.go). Single core we're 11% faster than main, and with all cores we're 6-11% slower.
  5. Blocker. Give the alias limit an opt-out (index/extract_refs.go). A valid 607KB spec now returns a nil model.
  6. Fatal that already exists on main. See below.
  7. Nit in datamodel/low/integer_decimal.go, then refresh this PR description, because the CI link and head SHA are stale.

Already broken on main: fix it here

A path item $ref loop kills the process in both v2 and v3, at seekRefEnd in index/utility_methods.go. It's outside this diff, so it gets no inline comment. It's the same family of bug this PR repairs.

x-a: {$ref: '#/x-b'}
x-b: {$ref: '#/x-a'}
paths:
  /a:
    $ref: '#/x-a'

Bound the recursion with a depth limit or a seen set, and report a build error instead. Add a v2 and a v3 test that both return an error and a process that's still alive.

How we keep this fast

Speed is the whole point of libopenapi. No merge until every number below is at or under main. "Noisy" is not an excuse, and I've used it once already on this PR.

Benchmark (median, ms) main base This PR Change
Index, 4.4MB YAML fixture 27.86 31.64 +13.6%
Index, 4.7MB JSON fixture 15.97 16.89 +5.8%
Index, 400KB YAML fixture 3.52 3.79 +7.7%
CompareDocuments, hashes cached 96.4 106.7 +10.7%
CompareDocuments, fresh models 178.5 189.8 +6.3%

Full model build came out flat on medians and +2-3% on best runs, so that one gets re-measured after the index fix. Compare memory is up 5%.

The rules for measuring:

  • Benchmark the merge-base against this branch, with nothing else running on the machine.
  • Alternate the runs (base, branch, base, branch) for at least 6 rounds. Short runs lie. A 2-iteration run told me +61% on the same code that measures +14% here.
  • Report median and best run, plus B/op and allocs/op.
  • Use -race and a repeated-run determinism check for anything concurrent.
  • Post the final table in this PR.

Worth a release note

TotalBreakingChanges no longer double counts allOf, and a retargeted $ref now reports nested changes. Both are correct, and both will move the numbers people see in openapi-changes and vacuum.

Get that list done and she's ready.

Comment thread index/resolver_relatives.go Resolved
Comment thread what-changed/model/schema_comparison.go Resolved
Comment thread index/extract_refs.go Resolved
Comment thread index/extract_refs.go Resolved
Comment thread what-changed/model/schema.go Resolved
Comment thread datamodel/low/integer_decimal.go Resolved
daveshanley and others added 27 commits October 31, 2026 16:38
When a schema $ref was replaced by an inline schema, an inline schema by
a $ref, or a $ref pointed somewhere else, CompareSchemas recorded a
non-breaking $ref change and returned, so changes in the new schema were
never reported. A $ref to a schema with type: int becoming an inline
type: string was a single non-breaking change.

Keep the $ref change, then compare the two schemas as it would compare
two inline schemas. It still stops at the $ref change, now keeping that
change instead of returning nil, when either side doesn't resolve, is on
a reference loop, or comes from a document that was not checked for
circular references (a loop can't be recognised there), and when a 3.1
$ref with sibling keywords is involved, since its internal allOf form
doesn't line up with the referenced schema field by field. Rolodex gains
HasCheckedForCircularReferences for that check.

The helpers that recognise equivalent forms now look through a
reference, so a change made through a $ref is reported like the same
inline change: the object composition handling from #650 ($ref: Thing
becoming anyOf: [$ref: Thing, type: 'null']), the simple allOf view, and
a type array matching an anyOf of scalar types.

Fixes #663
@daveshanley
daveshanley force-pushed the codex/october-issue-repairs branch from 4bf027e to 90e6e07 Compare October 31, 2026 21:05

Copy link
Copy Markdown
Member Author

Performance results for c339b4bc3832ec5f9aa26daa2ec12b79bc75fe26 against main c6910f28a4bb8f8a115c5f584c74bd4605455051.

Six alternating main/PR pairs, separate processes, Go 1.26.4, Apple M4 Max, 16 logical CPUs, 2 seconds per benchmark. Both binaries use the same harness and fixtures. Interrupted samples were discarded; the final samples all passed.

Benchmark Median ms main → PR Best ms main → PR Time change Median B/op main → PR Median allocs/op main → PR
Index/stripe.yaml 26.963 → 25.673 26.400 → 24.834 -4.78% 40,910,543 → 40,870,398 405,883 → 404,618
Index/k8s.json 15.557 → 15.127 14.762 → 14.613 -2.77% 24,567,896 → 24,556,632 297,471 → 297,112
Index/asana.yaml 3.412 → 3.227 3.278 → 3.174 -5.43% 4,769,048 → 4,769,143 53,026 → 53,028
Build/stripe.yaml 113.641 → 114.310 111.980 → 110.800 +0.59% 98,502,149 → 98,454,740 1,494,481 → 1,493,244
Build/k8s.json 42.078 → 41.955 41.580 → 41.425 -0.29% 72,766,728 → 72,757,276 547,483 → 547,128
CompareFresh 184.297 → 163.159 175.223 → 159.470 -11.47% 295,556,966 → 273,905,138 2,077,226 → 1,831,326
CompareCached 89.696 → 74.067 87.459 → 71.880 -17.42% 123,960,220 → 102,422,546 1,284,050 → 1,038,048

Indexing is faster on all three fixtures. Fresh and cached comparison are faster and use less memory than main, which meets the comparison review's time/memory requirement.

The full-build results are mixed: Kubernetes is 0.29% faster; Stripe's median is 0.59% slower, although its best sample is faster. Asana indexing uses approximately 95 more B/op and 2 more allocs/op. These measurements do not establish that every operation is at or below main on every metric.

The full-build harness uses Swagger 2 for Kubernetes and OpenAPI 3 for Stripe. Stripe's known circular-reference warning is allowed only when a model is returned. Fresh comparison clears the hash cache and builds both models outside each timed iteration; its one-time fixture preparation is included. Cached comparison warms the comparison before resetting the timer.

Validation on this head: all 31,372 statements covered, zero uncovered blocks; Linux coverage, full race suite, and Windows tests pass. CodeQL passes. Codecov reports 100.00% project and 100.00% patch coverage.

Identical benchmark harness used for both revisions

Run each binary from its corresponding checkout:

./benchmark.test -test.run '^$' -test.bench '^BenchmarkP(Index|Build|CompareFresh|CompareCached)$' -test.benchmem -test.benchtime=2s -test.count=1
package libopenapi

import (
	"os"
	"strings"
	"testing"

	"github.com/pb33f/go-yaml"
	"github.com/pb33f/libopenapi/datamodel/low"
	"github.com/pb33f/libopenapi/index"
	"github.com/pb33f/libopenapi/what-changed/model"
)

func BenchmarkPIndex(b *testing.B) {
	for _, f := range []string{"stripe.yaml", "k8s.json", "asana.yaml"} {
		data, _ := os.ReadFile("test_specs/" + f)
		b.Run(f, func(b *testing.B) {
			var root yaml.Node
			_ = yaml.Unmarshal(data, &root)
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				_ = index.NewSpecIndexWithConfig(&root, index.CreateClosedAPIIndexConfig())
			}
		})
	}
}

func BenchmarkPBuild(b *testing.B) {
	for _, f := range []string{"stripe.yaml", "k8s.json"} {
		data, _ := os.ReadFile("test_specs/" + f)
		b.Run(f, func(b *testing.B) {
			b.ReportAllocs()
			for i := 0; i < b.N; i++ {
				d, err := NewDocument(data)
				if err != nil {
					b.Fatal(err)
				}
				var built bool
				if f == "k8s.json" {
					model, buildErr := d.BuildV2Model()
					built, err = model != nil, buildErr
				} else {
					model, buildErr := d.BuildV3Model()
					built, err = model != nil, buildErr
				}
				// Stripe reports known circular references with a usable model.
				if !built { b.Fatal(err) }

			}
		})
	}
}

func BenchmarkPCompareFresh(b *testing.B) {
	data, _ := os.ReadFile("test_specs/stripe.yaml")
	mod := []byte(strings.ReplaceAll(string(data), "maxLength: 5000", "maxLength: 4000"))
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		b.StopTimer()
		low.ClearHashCache()
		l, _ := NewDocument(data)
		r, _ := NewDocument(mod)
		lm, _ := l.BuildV3Model()
		rm, _ := r.BuildV3Model()
		b.StartTimer()
		_ = model.CompareDocuments(lm.Model.GoLow(), rm.Model.GoLow())
	}
}

func BenchmarkPCompareCached(b *testing.B) {
	data, _ := os.ReadFile("test_specs/stripe.yaml")
	mod := []byte(strings.ReplaceAll(string(data), "maxLength: 5000", "maxLength: 4000"))
	l, _ := NewDocument(data)
	r, _ := NewDocument(mod)
	lm, _ := l.BuildV3Model()
	rm, _ := r.BuildV3Model()
	_ = model.CompareDocuments(lm.Model.GoLow(), rm.Model.GoLow())
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = model.CompareDocuments(lm.Model.GoLow(), rm.Model.GoLow())
	}
}

Copy link
Copy Markdown
Member Author

Reran the benchmarks on the requested ../demo/speed-test corpus: Petstore, Mistral, Neon, LaunchDarkly, Plaid, and Stripe. The earlier table used different fixtures and is not the result for this corpus.

Main: c6910f28a4bb8f8a115c5f584c74bd4605455051. PR: c339b4bc3832ec5f9aa26daa2ec12b79bc75fe26.
Go 1.26.4, Apple M4 Max, 16 logical CPUs. Six samples per commit for each of 30 cases, using identical benchmark code. Each sample uses Go's -benchtime=1s calibration. Main/PR order reverses on each pair. No competing Go build or test was detected; all 12 runs passed. Fixture hashes were unchanged after the run.

Operation Geometric mean of PR/main median time changes across six specs
Index −3.34%
Build −0.05%
Render +0.29%
Compare −1.99%
Bundle +0.15%

Remaining measured regression: Stripe rendering is 191.406 → 193.743 ms (+1.22%, p=0.015, n=6). This is the only timing increase that benchstat marks significant at its default 0.05 threshold. It is not evidence of across-the-board parity.

Significant timing improvements: Mistral indexing (−4.92%), Stripe indexing (−5.45%), Stripe building (−1.74%), and comparison on Mistral (−2.93%), Neon (−2.01%), and Plaid (−0.88%). Other timing differences did not meet the default significance threshold. Comparison allocations in bytes fall on all six fixtures, by 0.51–5.67%.

These are pipeline workloads: indexing excludes YAML parsing; Build includes parsing and model construction; Render repeatedly renders a built model; Compare includes parsing and building both documents; Bundle includes the full BundleBytes call. Compare uses the existing pipeline benchmark's first 200 string-type and first 300 nullable mutations, and clears the schema hash cache outside each timed iteration. Every comparison reports real changes, with identical change counts on main and PR in all samples. This is a count check, not a claim of complete report equivalence. Render includes the first render, as the existing pipeline benchmark does.

Fixture / operation Main ms PR ms Time delta Main B/op PR B/op Main allocs/op PR allocs/op
petstore.yaml/Index 0.249 0.249 -0.18% 317,136 317,154 4,040 4,041
petstore.yaml/Build 1.212 1.235 +1.86% 1,099,968 1,100,124 17,283 17,284
petstore.yaml/Render 0.831 0.833 +0.21% 591,363 591,361 10,383 10,384
petstore.yaml/Compare 4.386 4.187 -4.52% 3,916,076 3,693,960 49,850 47,582
petstore.yaml/Bundle 2.465 2.468 +0.13% 2,154,436 2,154,255 30,880 30,880
mistral.yaml/Index 2.752 2.616 -4.92% 3,848,124 3,840,334 42,260 42,018
mistral.yaml/Build 11.431 11.403 -0.25% 10,203,116 10,194,246 162,786 162,542
mistral.yaml/Render 13.081 12.989 -0.70% 9,285,098 9,285,146 144,099 144,098
mistral.yaml/Compare 36.594 35.524 -2.93% 43,703,582 43,231,800 459,532 450,830
mistral.yaml/Bundle 30.800 30.827 +0.09% 31,373,208 31,366,434 389,912 389,671
neon.yaml/Index 2.774 2.736 -1.39% 4,055,059 4,048,710 45,911 45,718
neon.yaml/Build 12.317 12.347 +0.25% 12,416,464 12,411,054 187,970 187,759
neon.yaml/Render 12.026 12.071 +0.38% 7,830,778 7,831,194 126,778 126,780
neon.yaml/Compare 37.522 36.768 -2.01% 42,248,851 41,142,014 504,706 493,083
neon.yaml/Bundle 31.701 31.843 +0.45% 30,031,650 30,023,188 386,118 385,900
ld.yaml/Index 12.222 11.816 -3.32% 17,802,616 17,779,149 214,292 213,563
ld.yaml/Build 56.502 56.405 -0.17% 58,250,878 58,069,020 908,636 904,726
ld.yaml/Render 62.094 62.332 +0.38% 40,759,650 40,841,492 660,656 661,266
ld.yaml/Compare 153.688 153.390 -0.19% 173,945,310 172,622,872 2,299,429 2,281,918
ld.yaml/Bundle 136.379 135.357 -0.75% 131,616,799 131,442,907 1,794,006 1,790,080
plaid.yml/Index 19.166 18.272 -4.66% 28,614,309 28,485,862 308,385 304,286
plaid.yml/Build 86.886 86.695 -0.22% 75,345,306 75,273,274 1,188,718 1,184,644
plaid.yml/Render 91.520 91.765 +0.27% 70,834,281 70,843,336 939,768 939,769
plaid.yml/Compare 234.730 232.660 -0.88% 235,294,394 232,359,674 2,912,164 2,876,014
plaid.yml/Bundle 201.985 203.511 +0.76% 187,952,798 187,843,928 2,431,192 2,427,074
stripe.yaml/Index 40.356 38.156 -5.45% 57,556,369 57,498,516 558,588 556,804
stripe.yaml/Build 158.812 156.056 -1.74% 133,077,416 132,985,118 2,003,099 2,001,322
stripe.yaml/Render 191.406 193.743 +1.22% 143,197,122 143,197,076 2,129,553 2,129,553
stripe.yaml/Compare 450.552 444.490 -1.35% 508,267,742 505,656,850 5,212,061 5,182,213
stripe.yaml/Bundle 2270.066 2274.883 +0.21% 1,876,092,280 1,876,492,956 16,943,532 16,941,937
Full benchstat output
goos: darwin
goarch: arm64
pkg: github.com/pb33f/libopenapi
cpu: Apple M4 Max
                                   │  base.txt   │             branch.txt             │
                                   │   sec/op    │    sec/op     vs base              │
DemoSpeed/petstore.yaml/Index-16     249.4µ ± 3%   249.0µ ±  3%       ~ (p=0.818 n=6)
DemoSpeed/petstore.yaml/Build-16     1.212m ± 1%   1.235m ± 12%       ~ (p=0.310 n=6)
DemoSpeed/petstore.yaml/Render-16    831.4µ ± 2%   833.1µ ±  5%       ~ (p=0.589 n=6)
DemoSpeed/petstore.yaml/Compare-16   4.386m ± 1%   4.187m ±  5%       ~ (p=0.065 n=6)
DemoSpeed/petstore.yaml/Bundle-16    2.465m ± 2%   2.468m ±  8%       ~ (p=0.937 n=6)
DemoSpeed/mistral.yaml/Index-16      2.752m ± 1%   2.616m ±  5%  -4.92% (p=0.009 n=6)
DemoSpeed/mistral.yaml/Build-16      11.43m ± 2%   11.40m ±  9%       ~ (p=0.937 n=6)
DemoSpeed/mistral.yaml/Render-16     13.08m ± 1%   12.99m ±  4%       ~ (p=0.394 n=6)
DemoSpeed/mistral.yaml/Compare-16    36.59m ± 3%   35.52m ±  2%  -2.93% (p=0.009 n=6)
DemoSpeed/mistral.yaml/Bundle-16     30.80m ± 1%   30.83m ±  2%       ~ (p=0.394 n=6)
DemoSpeed/neon.yaml/Index-16         2.774m ± 1%   2.736m ±  3%       ~ (p=0.394 n=6)
DemoSpeed/neon.yaml/Build-16         12.32m ± 2%   12.35m ± 12%       ~ (p=0.394 n=6)
DemoSpeed/neon.yaml/Render-16        12.03m ± 4%   12.07m ±  4%       ~ (p=0.485 n=6)
DemoSpeed/neon.yaml/Compare-16       37.52m ± 1%   36.77m ±  2%  -2.01% (p=0.026 n=6)
DemoSpeed/neon.yaml/Bundle-16        31.70m ± 2%   31.84m ±  2%       ~ (p=0.180 n=6)
DemoSpeed/ld.yaml/Index-16           12.22m ± 2%   11.82m ±  5%       ~ (p=0.065 n=6)
DemoSpeed/ld.yaml/Build-16           56.50m ± 1%   56.40m ±  2%       ~ (p=0.818 n=6)
DemoSpeed/ld.yaml/Render-16          62.09m ± 2%   62.33m ±  3%       ~ (p=0.485 n=6)
DemoSpeed/ld.yaml/Compare-16         153.7m ± 1%   153.4m ±  2%       ~ (p=0.589 n=6)
DemoSpeed/ld.yaml/Bundle-16          136.4m ± 2%   135.4m ±  2%       ~ (p=0.589 n=6)
DemoSpeed/plaid.yml/Index-16         19.17m ± 1%   18.27m ±  8%       ~ (p=0.065 n=6)
DemoSpeed/plaid.yml/Build-16         86.89m ± 1%   86.70m ±  2%       ~ (p=1.000 n=6)
DemoSpeed/plaid.yml/Render-16        91.52m ± 1%   91.76m ±  1%       ~ (p=0.240 n=6)
DemoSpeed/plaid.yml/Compare-16       234.7m ± 3%   232.7m ±  2%  -0.88% (p=0.041 n=6)
DemoSpeed/plaid.yml/Bundle-16        202.0m ± 3%   203.5m ±  2%       ~ (p=0.310 n=6)
DemoSpeed/stripe.yaml/Index-16       40.36m ± 4%   38.16m ±  2%  -5.45% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Build-16       158.8m ± 3%   156.1m ±  2%  -1.74% (p=0.041 n=6)
DemoSpeed/stripe.yaml/Render-16      191.4m ± 1%   193.7m ±  1%  +1.22% (p=0.015 n=6)
DemoSpeed/stripe.yaml/Compare-16     450.6m ± 1%   444.5m ±  5%       ~ (p=0.394 n=6)
DemoSpeed/stripe.yaml/Bundle-16       2.270 ± 1%    2.275 ±  2%       ~ (p=0.394 n=6)
geomean                              26.51m        26.24m        -1.00%

                                   │   base.txt   │             branch.txt             │
                                   │     B/op     │     B/op      vs base              │
DemoSpeed/petstore.yaml/Index-16     309.7Ki ± 0%   309.7Ki ± 0%       ~ (p=0.225 n=6)
DemoSpeed/petstore.yaml/Build-16     1.049Mi ± 0%   1.049Mi ± 0%       ~ (p=0.394 n=6)
DemoSpeed/petstore.yaml/Render-16    577.5Ki ± 0%   577.5Ki ± 0%       ~ (p=0.729 n=6)
DemoSpeed/petstore.yaml/Compare-16   3.735Mi ± 0%   3.523Mi ± 0%  -5.67% (p=0.002 n=6)
DemoSpeed/petstore.yaml/Bundle-16    2.055Mi ± 0%   2.054Mi ± 0%       ~ (p=0.394 n=6)
DemoSpeed/mistral.yaml/Index-16      3.670Mi ± 0%   3.662Mi ± 0%  -0.20% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Build-16      9.730Mi ± 0%   9.722Mi ± 0%  -0.09% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Render-16     8.855Mi ± 0%   8.855Mi ± 0%       ~ (p=0.240 n=6)
DemoSpeed/mistral.yaml/Compare-16    41.68Mi ± 0%   41.23Mi ± 0%  -1.08% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Bundle-16     29.92Mi ± 0%   29.91Mi ± 0%  -0.02% (p=0.015 n=6)
DemoSpeed/neon.yaml/Index-16         3.867Mi ± 0%   3.861Mi ± 0%  -0.16% (p=0.002 n=6)
DemoSpeed/neon.yaml/Build-16         11.84Mi ± 0%   11.84Mi ± 0%  -0.04% (p=0.002 n=6)
DemoSpeed/neon.yaml/Render-16        7.468Mi ± 0%   7.468Mi ± 0%       ~ (p=0.240 n=6)
DemoSpeed/neon.yaml/Compare-16       40.29Mi ± 0%   39.24Mi ± 0%  -2.62% (p=0.002 n=6)
DemoSpeed/neon.yaml/Bundle-16        28.64Mi ± 0%   28.63Mi ± 0%  -0.03% (p=0.002 n=6)
DemoSpeed/ld.yaml/Index-16           16.98Mi ± 0%   16.96Mi ± 0%  -0.13% (p=0.002 n=6)
DemoSpeed/ld.yaml/Build-16           55.55Mi ± 0%   55.38Mi ± 0%  -0.31% (p=0.002 n=6)
DemoSpeed/ld.yaml/Render-16          38.87Mi ± 0%   38.95Mi ± 0%       ~ (p=0.589 n=6)
DemoSpeed/ld.yaml/Compare-16         165.9Mi ± 0%   164.6Mi ± 0%  -0.76% (p=0.002 n=6)
DemoSpeed/ld.yaml/Bundle-16          125.5Mi ± 0%   125.4Mi ± 0%  -0.13% (p=0.002 n=6)
DemoSpeed/plaid.yml/Index-16         27.29Mi ± 0%   27.17Mi ± 0%  -0.45% (p=0.002 n=6)
DemoSpeed/plaid.yml/Build-16         71.85Mi ± 0%   71.79Mi ± 0%  -0.10% (p=0.002 n=6)
DemoSpeed/plaid.yml/Render-16        67.55Mi ± 0%   67.56Mi ± 0%       ~ (p=0.180 n=6)
DemoSpeed/plaid.yml/Compare-16       224.4Mi ± 0%   221.6Mi ± 0%  -1.25% (p=0.002 n=6)
DemoSpeed/plaid.yml/Bundle-16        179.2Mi ± 0%   179.1Mi ± 0%       ~ (p=0.065 n=6)
DemoSpeed/stripe.yaml/Index-16       54.89Mi ± 0%   54.83Mi ± 0%  -0.10% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Build-16       126.9Mi ± 0%   126.8Mi ± 0%  -0.07% (p=0.026 n=6)
DemoSpeed/stripe.yaml/Render-16      136.6Mi ± 0%   136.6Mi ± 0%       ~ (p=0.937 n=6)
DemoSpeed/stripe.yaml/Compare-16     484.7Mi ± 0%   482.2Mi ± 0%  -0.51% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Bundle-16      1.747Gi ± 0%   1.748Gi ± 0%       ~ (p=0.310 n=6)
geomean                              24.84Mi        24.73Mi       -0.46%

                                   │  base.txt   │            branch.txt             │
                                   │  allocs/op  │  allocs/op   vs base              │
DemoSpeed/petstore.yaml/Index-16     4.040k ± 0%   4.041k ± 0%       ~ (p=1.000 n=6)
DemoSpeed/petstore.yaml/Build-16     17.28k ± 0%   17.28k ± 0%       ~ (p=0.182 n=6)
DemoSpeed/petstore.yaml/Render-16    10.38k ± 0%   10.38k ± 0%       ~ (p=0.567 n=6)
DemoSpeed/petstore.yaml/Compare-16   49.85k ± 0%   47.58k ± 0%  -4.55% (p=0.002 n=6)
DemoSpeed/petstore.yaml/Bundle-16    30.88k ± 0%   30.88k ± 0%       ~ (p=1.000 n=6)
DemoSpeed/mistral.yaml/Index-16      42.26k ± 0%   42.02k ± 0%  -0.57% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Build-16      162.8k ± 0%   162.5k ± 0%  -0.15% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Render-16     144.1k ± 0%   144.1k ± 0%       ~ (p=0.665 n=6)
DemoSpeed/mistral.yaml/Compare-16    459.5k ± 0%   450.8k ± 0%  -1.89% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Bundle-16     389.9k ± 0%   389.7k ± 0%  -0.06% (p=0.002 n=6)
DemoSpeed/neon.yaml/Index-16         45.91k ± 0%   45.72k ± 0%  -0.42% (p=0.002 n=6)
DemoSpeed/neon.yaml/Build-16         188.0k ± 0%   187.8k ± 0%  -0.11% (p=0.002 n=6)
DemoSpeed/neon.yaml/Render-16        126.8k ± 0%   126.8k ± 0%       ~ (p=0.502 n=6)
DemoSpeed/neon.yaml/Compare-16       504.7k ± 0%   493.1k ± 0%  -2.30% (p=0.002 n=6)
DemoSpeed/neon.yaml/Bundle-16        386.1k ± 0%   385.9k ± 0%  -0.06% (p=0.002 n=6)
DemoSpeed/ld.yaml/Index-16           214.3k ± 0%   213.6k ± 0%  -0.34% (p=0.002 n=6)
DemoSpeed/ld.yaml/Build-16           908.6k ± 0%   904.7k ± 0%  -0.43% (p=0.002 n=6)
DemoSpeed/ld.yaml/Render-16          660.7k ± 0%   661.3k ± 0%       ~ (p=0.567 n=6)
DemoSpeed/ld.yaml/Compare-16         2.299M ± 0%   2.282M ± 0%  -0.76% (p=0.002 n=6)
DemoSpeed/ld.yaml/Bundle-16          1.794M ± 0%   1.790M ± 0%  -0.22% (p=0.002 n=6)
DemoSpeed/plaid.yml/Index-16         308.4k ± 0%   304.3k ± 0%  -1.33% (p=0.002 n=6)
DemoSpeed/plaid.yml/Build-16         1.189M ± 0%   1.185M ± 0%  -0.34% (p=0.002 n=6)
DemoSpeed/plaid.yml/Render-16        939.8k ± 0%   939.8k ± 0%       ~ (p=0.156 n=6)
DemoSpeed/plaid.yml/Compare-16       2.912M ± 0%   2.876M ± 0%  -1.24% (p=0.002 n=6)
DemoSpeed/plaid.yml/Bundle-16        2.431M ± 0%   2.427M ± 0%  -0.17% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Index-16       558.6k ± 0%   556.8k ± 0%  -0.32% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Build-16       2.003M ± 0%   2.001M ± 0%  -0.09% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Render-16      2.130M ± 0%   2.130M ± 0%       ~ (p=1.000 n=6)
DemoSpeed/stripe.yaml/Compare-16     5.212M ± 0%   5.182M ± 0%  -0.57% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Bundle-16      16.94M ± 0%   16.94M ± 0%  -0.01% (p=0.002 n=6)
geomean                              343.1k        341.2k       -0.53%

                                   │  base.txt  │             branch.txt             │
                                   │ changes/op │ changes/op  vs base                │
DemoSpeed/petstore.yaml/Compare-16   35.00 ± 0%   35.00 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/mistral.yaml/Compare-16    200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/neon.yaml/Compare-16       205.0 ± 0%   205.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/ld.yaml/Compare-16         204.0 ± 0%   204.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/plaid.yml/Compare-16       500.0 ± 0%   500.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/stripe.yaml/Compare-16     500.0 ± 0%   500.0 ± 0%       ~ (p=1.000 n=6) ¹
geomean                              204.5        204.5       +0.00%
¹ all samples are equal

Reproducer: identical Go benchmark for both revisions

Set LIBOPENAPI_DEMO_SPEED_DIR to the corpus directory. Compile this as an external test file against each revision, then run each binary six times in alternating pair order:

LIBOPENAPI_DEMO_SPEED_DIR=/path/to/demo/speed-test ./benchmark.test \
  -test.run '^$' -test.bench '^BenchmarkDemoSpeed$' \
  -test.benchmem -test.benchtime=1s -test.count=1
package libopenapi_test

import (
	"bytes"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"testing"

	"github.com/pb33f/go-yaml"
	"github.com/pb33f/libopenapi"
	"github.com/pb33f/libopenapi/bundler"
	"github.com/pb33f/libopenapi/datamodel"
	"github.com/pb33f/libopenapi/datamodel/low"
	"github.com/pb33f/libopenapi/index"
)

func demoSpeedConfig() *datamodel.DocumentConfiguration {
	return &datamodel.DocumentConfiguration{
		IgnorePolymorphicCircularReferences: true,
		IgnoreArrayCircularReferences:       true,
		Logger:                              slog.New(slog.NewTextHandler(io.Discard, nil)),
	}
}

func demoSpeedDocument(b *testing.B, data []byte) libopenapi.Document {
	b.Helper()
	doc, err := libopenapi.NewDocumentWithConfiguration(data, demoSpeedConfig())
	if err != nil {
		b.Fatal(err)
	}
	return doc
}

func demoSpeedBuild(b *testing.B, data []byte) libopenapi.Document {
	b.Helper()
	doc := demoSpeedDocument(b, data)
	m, err := doc.BuildV3Model()
	if m == nil {
		b.Fatalf("nil model: %v", err)
	}
	return doc
}

// BenchmarkDemoSpeed uses the user's full demo corpus and the same public
// pipeline operations as pipeline_bench_test.go. No fixture is changed on disk.
func BenchmarkDemoSpeed(b *testing.B) {
	dir := os.Getenv("LIBOPENAPI_DEMO_SPEED_DIR")
	if dir == "" {
		b.Fatal("LIBOPENAPI_DEMO_SPEED_DIR must name demo/speed-test")
	}
	for _, file := range []string{"petstore.yaml", "mistral.yaml", "neon.yaml", "ld.yaml", "plaid.yml", "stripe.yaml"} {
		data, err := os.ReadFile(filepath.Join(dir, file))
		if err != nil {
			b.Fatal(err)
		}
		b.Run(file, func(b *testing.B) {
			b.Run("Index", func(b *testing.B) {
				var root yaml.Node
				if err := yaml.Unmarshal(data, &root); err != nil {
					b.Fatal(err)
				}
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					idx := index.NewSpecIndexWithConfig(&root, index.CreateClosedAPIIndexConfig())
					if idx == nil {
						b.Fatal("nil index")
					}
				}
			})
			b.Run("Build", func(b *testing.B) {
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					demoSpeedBuild(b, data)
				}
			})
			b.Run("Render", func(b *testing.B) {
				doc := demoSpeedBuild(b, data)
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					out, err := doc.Render()
					if err != nil || len(out) == 0 {
						b.Fatalf("render: %v", err)
					}
				}
			})
			b.Run("Compare", func(b *testing.B) {
				// Use the mutation from the existing pipeline benchmark. Assert that
				// every corpus member produces real changes, not an identity comparison.
				right := bytes.Replace(data, []byte("type: string"), []byte("type: integer"), 200)
				right = bytes.Replace(right, []byte("nullable: true"), []byte("nullable: false"), 300)
				if bytes.Equal(data, right) {
					b.Fatal("mutation made no changes")
				}
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					b.StopTimer()
					low.ClearHashCache()
					b.StartTimer()
					leftDoc, rightDoc := demoSpeedDocument(b, data), demoSpeedDocument(b, right)
					changes, err := libopenapi.CompareDocuments(leftDoc, rightDoc)
					b.StopTimer()
					if changes == nil || changes.TotalChanges() == 0 {
						b.Fatalf("no changes: %v", err)
					}
					b.ReportMetric(float64(changes.TotalChanges()), "changes/op")
					b.StartTimer()
				}
				b.StopTimer()
				low.ClearHashCache()
			})
			b.Run("Bundle", func(b *testing.B) {
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					out, err := bundler.BundleBytes(data, demoSpeedConfig())
					if len(out) == 0 {
						b.Fatalf("empty bundle: %v", err)
					}
				}
			})
		})
	}
}
Fixture sizes and SHA-256 hashes
{
  "ld.yaml": {
    "bytes": 1927409,
    "sha256": "6e282f8852fe1d38527dfb7b6e90c27fe9505d66ed90f87c5b6a30fd8594e86c"
  },
  "mistral.yaml": {
    "bytes": 292380,
    "sha256": "37659aafcad6a8219609636db74a7481b24b6cbb0a54f6d184278b584f5269df"
  },
  "neon.yaml": {
    "bytes": 369544,
    "sha256": "958d5c6f86970418ad104dd87e7efb2185ef0ef16bd25c2502c382f82cd64a1b"
  },
  "petstore.yaml": {
    "bytes": 21302,
    "sha256": "ef2b2dea0ef6a09ba6247017ffd5a6405c689ec9b11e73d1b61b6e2047ce35e8"
  },
  "plaid.yml": {
    "bytes": 2870618,
    "sha256": "8addecd0b74aeb23ae844b349d8a4a2e1cbbb7ed2210f57d31cf644c37af7fa3"
  },
  "stripe.yaml": {
    "bytes": 6051965,
    "sha256": "ae4d4352c52ef5246904e15bacc639fd381494022f19eab6f74b36f47c85a1b4"
  }
}

Copy link
Copy Markdown
Member Author

Vacuum compatibility is fixed at 9d96a62c0c0524313c7866de8ae1435c28ba6f2e.

Unmodified Vacuum main 1fd76af2 passes all 36 test packages, race checks for motor and functions/openapi, and 20 repeated race-enabled runs of TestCircularReferences_MultiFile_LazyFS. Unmodified openapi-changes main 1750dae3 passes all seven test packages plus race checks for its command, git, and change-count packages. The isolated builds replace only libopenapi; neither downstream source tree was patched.

All six demo/speed-test specifications pass CLI checks. Vacuum reports the same 39,248 findings as main. Its --original output matches exactly for Plaid (257 findings) and Stripe (889). Stripe's circular journeys change as expected from the fixed journey ownership. openapi-changes preserves the change records, with existing path-selection variability; Plaid's breaking summary correctly removes duplicate allOf counts. Canonical JSON, summary, Markdown, and HTML workflows also pass.

CI passes Linux coverage, the full race suite, and Windows tests. CodeQL passes. Codecov confirms 100% project and 100% patch coverage. Local coverage is 31,437/31,437 statements.

Fresh performance comparison: main c6910f28 against repaired PR 9d96a62c, Go 1.26.4, Apple M4 Max, six balanced AB/BA pairs, separate precompiled processes, 1s benchtime, all six requested specifications and five pipeline operations. The previous report's harness and reproduction method are unchanged; the PR binary was rebuilt. Two interrupted samples were discarded when a competing Go process appeared, then rerun. All 12 accepted runs pass, source fixture hashes are unchanged, and comparison change counts match.

Geometric means of per-spec median time ratios: indexing −4.02%, building −1.31%, rendering −0.33%, end-to-end comparison −2.42%, bundling −0.81%. These are descriptive aggregates, not significance claims. Stripe rendering is +1.02% (191.361 → 193.303 ms, p=0.394); Stripe comparison is +0.04% and bundling +0.57%. Some accepted runs contain large timing outliers, especially Neon and Stripe indexing; they are retained in the statistics. This run does not prove every operation is faster or establish tight performance parity. It supersedes earlier timing numbers for the previous PR head.

Fixture / operation Main ms PR ms Time delta Main B/op PR B/op Main allocs/op PR allocs/op
petstore.yaml/Index 0.251 0.242 -3.24% 317,131 317,114 4,040 4,040
petstore.yaml/Build 1.235 1.168 -5.49% 1,100,036 1,099,836 17,283 17,282
petstore.yaml/Render 0.835 0.825 -1.14% 591,369 591,371 10,384 10,384
petstore.yaml/Compare 4.450 4.122 -7.37% 3,916,119 3,694,101 49,850 47,582
petstore.yaml/Bundle 2.533 2.423 -4.34% 2,154,434 2,154,248 30,880 30,880
mistral.yaml/Index 2.709 2.584 -4.63% 3,848,170 3,840,278 42,261 42,018
mistral.yaml/Build 11.486 11.313 -1.51% 10,201,767 10,195,920 162,786 162,544
mistral.yaml/Render 13.103 12.851 -1.92% 9,288,025 9,285,110 144,116 144,098
mistral.yaml/Compare 36.529 35.436 -2.99% 43,704,902 43,230,486 459,527 450,816
mistral.yaml/Bundle 30.782 30.490 -0.95% 31,373,428 31,369,282 389,914 389,672
neon.yaml/Index 2.771 2.658 -4.08% 4,054,792 4,048,858 45,908 45,718
neon.yaml/Build 12.272 12.405 +1.08% 12,417,780 12,408,949 187,972 187,758
neon.yaml/Render 11.972 11.933 -0.33% 7,831,158 7,830,780 126,780 126,778
neon.yaml/Compare 37.214 36.478 -1.98% 42,245,124 41,141,744 504,692 493,076
neon.yaml/Bundle 31.725 31.654 -0.22% 30,028,959 30,020,016 386,116 385,902
ld.yaml/Index 12.264 11.896 -3.00% 17,803,179 17,780,014 214,294 213,570
ld.yaml/Build 56.163 56.092 -0.13% 58,222,693 58,073,610 908,646 904,726
ld.yaml/Render 62.126 62.033 -0.15% 40,759,703 40,759,836 660,656 660,657
ld.yaml/Compare 153.253 152.038 -0.79% 173,962,864 172,600,388 2,299,400 2,281,887
ld.yaml/Bundle 132.990 133.799 +0.61% 131,588,563 131,421,832 1,794,004 1,790,072
plaid.yml/Index 18.949 18.075 -4.61% 28,616,533 28,485,951 308,412 304,303
plaid.yml/Build 87.369 86.561 -0.92% 75,372,127 75,214,023 1,188,740 1,184,634
plaid.yml/Render 91.034 91.555 +0.57% 70,840,320 70,837,433 939,768 939,769
plaid.yml/Compare 231.858 228.917 -1.27% 235,213,315 232,270,134 2,911,966 2,875,996
plaid.yml/Bundle 201.367 200.451 -0.45% 187,942,388 187,810,770 2,431,170 2,427,047
stripe.yaml/Index 40.207 38.370 -4.57% 57,559,445 57,505,567 558,604 556,810
stripe.yaml/Build 157.876 156.648 -0.78% 133,028,668 132,964,268 2,003,121 2,001,314
stripe.yaml/Render 191.361 193.303 +1.02% 143,197,017 143,196,898 2,129,552 2,129,552
stripe.yaml/Compare 449.847 450.042 +0.04% 508,338,104 505,588,326 5,212,662 5,182,220
stripe.yaml/Bundle 2238.785 2251.602 +0.57% 1,876,353,868 1,876,390,604 16,943,591 16,941,880
Complete benchstat results
goos: darwin
goarch: arm64
pkg: github.com/pb33f/libopenapi
cpu: Apple M4 Max
                                   │  base.txt   │             branch.txt              │
                                   │   sec/op    │    sec/op      vs base              │
DemoSpeed/petstore.yaml/Index-16     250.6µ ± 8%   242.5µ ±   7%       ~ (p=0.065 n=6)
DemoSpeed/petstore.yaml/Build-16     1.235m ± 6%   1.168m ±   6%       ~ (p=0.065 n=6)
DemoSpeed/petstore.yaml/Render-16    834.5µ ± 7%   825.0µ ±   6%       ~ (p=0.485 n=6)
DemoSpeed/petstore.yaml/Compare-16   4.450m ± 6%   4.122m ±   6%  -7.37% (p=0.002 n=6)
DemoSpeed/petstore.yaml/Bundle-16    2.533m ± 5%   2.423m ±   2%  -4.34% (p=0.041 n=6)
DemoSpeed/mistral.yaml/Index-16      2.709m ± 1%   2.584m ±   3%  -4.63% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Build-16      11.49m ± 2%   11.31m ±   1%  -1.51% (p=0.015 n=6)
DemoSpeed/mistral.yaml/Render-16     13.10m ± 4%   12.85m ±   4%       ~ (p=0.180 n=6)
DemoSpeed/mistral.yaml/Compare-16    36.53m ± 1%   35.44m ±   5%       ~ (p=0.132 n=6)
DemoSpeed/mistral.yaml/Bundle-16     30.78m ± 2%   30.49m ±  10%       ~ (p=1.000 n=6)
DemoSpeed/neon.yaml/Index-16         2.771m ± 3%   2.658m ±   6%       ~ (p=0.180 n=6)
DemoSpeed/neon.yaml/Build-16         12.27m ± 3%   12.41m ±  58%       ~ (p=0.589 n=6)
DemoSpeed/neon.yaml/Render-16        11.97m ± 5%   11.93m ± 201%       ~ (p=1.000 n=6)
DemoSpeed/neon.yaml/Compare-16       37.21m ± 6%   36.48m ±  11%       ~ (p=0.132 n=6)
DemoSpeed/neon.yaml/Bundle-16        31.73m ± 2%   31.65m ± 191%       ~ (p=0.699 n=6)
DemoSpeed/ld.yaml/Index-16           12.26m ± 8%   11.90m ±   3%  -3.00% (p=0.041 n=6)
DemoSpeed/ld.yaml/Build-16           56.16m ± 2%   56.09m ±   2%       ~ (p=1.000 n=6)
DemoSpeed/ld.yaml/Render-16          62.13m ± 2%   62.03m ±   1%       ~ (p=0.818 n=6)
DemoSpeed/ld.yaml/Compare-16         153.3m ± 2%   152.0m ±   3%       ~ (p=0.394 n=6)
DemoSpeed/ld.yaml/Bundle-16          133.0m ± 7%   133.8m ±   2%       ~ (p=0.818 n=6)
DemoSpeed/plaid.yml/Index-16         18.95m ± 2%   18.07m ±   0%  -4.61% (p=0.002 n=6)
DemoSpeed/plaid.yml/Build-16         87.37m ± 2%   86.56m ±   1%       ~ (p=0.310 n=6)
DemoSpeed/plaid.yml/Render-16        91.03m ± 2%   91.55m ±   2%       ~ (p=0.589 n=6)
DemoSpeed/plaid.yml/Compare-16       231.9m ± 2%   228.9m ±   1%       ~ (p=0.093 n=6)
DemoSpeed/plaid.yml/Bundle-16        201.4m ± 2%   200.5m ±   3%       ~ (p=0.818 n=6)
DemoSpeed/stripe.yaml/Index-16       40.21m ± 3%   38.37m ± 353%       ~ (p=0.065 n=6)
DemoSpeed/stripe.yaml/Build-16       157.9m ± 3%   156.6m ±   4%       ~ (p=0.394 n=6)
DemoSpeed/stripe.yaml/Render-16      191.4m ± 1%   193.3m ±   3%       ~ (p=0.394 n=6)
DemoSpeed/stripe.yaml/Compare-16     449.8m ± 3%   450.0m ±   5%       ~ (p=0.937 n=6)
DemoSpeed/stripe.yaml/Bundle-16       2.239 ± 2%    2.252 ±   2%       ~ (p=0.310 n=6)
geomean                              26.47m        26.00m         -1.79%

                                   │   base.txt   │             branch.txt             │
                                   │     B/op     │     B/op      vs base              │
DemoSpeed/petstore.yaml/Index-16     309.7Ki ± 0%   309.7Ki ± 0%       ~ (p=0.223 n=6)
DemoSpeed/petstore.yaml/Build-16     1.049Mi ± 0%   1.049Mi ± 0%  -0.02% (p=0.041 n=6)
DemoSpeed/petstore.yaml/Render-16    577.5Ki ± 0%   577.5Ki ± 0%       ~ (p=0.853 n=6)
DemoSpeed/petstore.yaml/Compare-16   3.735Mi ± 0%   3.523Mi ± 0%  -5.67% (p=0.002 n=6)
DemoSpeed/petstore.yaml/Bundle-16    2.055Mi ± 0%   2.054Mi ± 0%       ~ (p=0.132 n=6)
DemoSpeed/mistral.yaml/Index-16      3.670Mi ± 0%   3.662Mi ± 0%  -0.21% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Build-16      9.729Mi ± 0%   9.724Mi ± 0%  -0.06% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Render-16     8.858Mi ± 0%   8.855Mi ± 0%       ~ (p=0.132 n=6)
DemoSpeed/mistral.yaml/Compare-16    41.68Mi ± 0%   41.23Mi ± 0%  -1.09% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Bundle-16     29.92Mi ± 0%   29.92Mi ± 0%  -0.01% (p=0.041 n=6)
DemoSpeed/neon.yaml/Index-16         3.867Mi ± 0%   3.861Mi ± 0%  -0.15% (p=0.002 n=6)
DemoSpeed/neon.yaml/Build-16         11.84Mi ± 0%   11.83Mi ± 0%  -0.07% (p=0.015 n=6)
DemoSpeed/neon.yaml/Render-16        7.468Mi ± 0%   7.468Mi ± 1%       ~ (p=1.000 n=6)
DemoSpeed/neon.yaml/Compare-16       40.29Mi ± 0%   39.24Mi ± 0%  -2.61% (p=0.002 n=6)
DemoSpeed/neon.yaml/Bundle-16        28.64Mi ± 0%   28.63Mi ± 0%       ~ (p=0.065 n=6)
DemoSpeed/ld.yaml/Index-16           16.98Mi ± 0%   16.96Mi ± 0%  -0.13% (p=0.002 n=6)
DemoSpeed/ld.yaml/Build-16           55.53Mi ± 0%   55.38Mi ± 0%  -0.26% (p=0.002 n=6)
DemoSpeed/ld.yaml/Render-16          38.87Mi ± 0%   38.87Mi ± 0%       ~ (p=0.485 n=6)
DemoSpeed/ld.yaml/Compare-16         165.9Mi ± 0%   164.6Mi ± 0%  -0.78% (p=0.002 n=6)
DemoSpeed/ld.yaml/Bundle-16          125.5Mi ± 0%   125.3Mi ± 0%  -0.13% (p=0.002 n=6)
DemoSpeed/plaid.yml/Index-16         27.29Mi ± 0%   27.17Mi ± 0%  -0.46% (p=0.002 n=6)
DemoSpeed/plaid.yml/Build-16         71.88Mi ± 0%   71.73Mi ± 0%  -0.21% (p=0.002 n=6)
DemoSpeed/plaid.yml/Render-16        67.56Mi ± 0%   67.56Mi ± 0%       ~ (p=0.937 n=6)
DemoSpeed/plaid.yml/Compare-16       224.3Mi ± 0%   221.5Mi ± 0%  -1.25% (p=0.002 n=6)
DemoSpeed/plaid.yml/Bundle-16        179.2Mi ± 0%   179.1Mi ± 0%  -0.07% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Index-16       54.89Mi ± 0%   54.84Mi ± 0%  -0.09% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Build-16       126.9Mi ± 0%   126.8Mi ± 0%       ~ (p=0.240 n=6)
DemoSpeed/stripe.yaml/Render-16      136.6Mi ± 0%   136.6Mi ± 0%       ~ (p=0.699 n=6)
DemoSpeed/stripe.yaml/Compare-16     484.8Mi ± 0%   482.2Mi ± 0%  -0.54% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Bundle-16      1.747Gi ± 0%   1.748Gi ± 0%       ~ (p=1.000 n=6)
geomean                              24.84Mi        24.72Mi       -0.47%

                                   │  base.txt   │            branch.txt             │
                                   │  allocs/op  │  allocs/op   vs base              │
DemoSpeed/petstore.yaml/Index-16     4.040k ± 0%   4.040k ± 0%       ~ (p=1.000 n=6)
DemoSpeed/petstore.yaml/Build-16     17.28k ± 0%   17.28k ± 0%  -0.01% (p=0.032 n=6)
DemoSpeed/petstore.yaml/Render-16    10.38k ± 0%   10.38k ± 0%       ~ (p=1.000 n=6)
DemoSpeed/petstore.yaml/Compare-16   49.85k ± 0%   47.58k ± 0%  -4.55% (p=0.002 n=6)
DemoSpeed/petstore.yaml/Bundle-16    30.88k ± 0%   30.88k ± 0%       ~ (p=0.238 n=6)
DemoSpeed/mistral.yaml/Index-16      42.26k ± 0%   42.02k ± 0%  -0.58% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Build-16      162.8k ± 0%   162.5k ± 0%  -0.15% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Render-16     144.1k ± 0%   144.1k ± 0%       ~ (p=0.104 n=6)
DemoSpeed/mistral.yaml/Compare-16    459.5k ± 0%   450.8k ± 0%  -1.90% (p=0.002 n=6)
DemoSpeed/mistral.yaml/Bundle-16     389.9k ± 0%   389.7k ± 0%  -0.06% (p=0.002 n=6)
DemoSpeed/neon.yaml/Index-16         45.91k ± 0%   45.72k ± 0%  -0.41% (p=0.002 n=6)
DemoSpeed/neon.yaml/Build-16         188.0k ± 0%   187.8k ± 0%  -0.11% (p=0.002 n=6)
DemoSpeed/neon.yaml/Render-16        126.8k ± 0%   126.8k ± 0%       ~ (p=0.972 n=6)
DemoSpeed/neon.yaml/Compare-16       504.7k ± 0%   493.1k ± 0%  -2.30% (p=0.002 n=6)
DemoSpeed/neon.yaml/Bundle-16        386.1k ± 0%   385.9k ± 0%  -0.06% (p=0.002 n=6)
DemoSpeed/ld.yaml/Index-16           214.3k ± 0%   213.6k ± 0%  -0.34% (p=0.002 n=6)
DemoSpeed/ld.yaml/Build-16           908.6k ± 0%   904.7k ± 0%  -0.43% (p=0.002 n=6)
DemoSpeed/ld.yaml/Render-16          660.7k ± 0%   660.7k ± 0%       ~ (p=0.732 n=6)
DemoSpeed/ld.yaml/Compare-16         2.299M ± 0%   2.282M ± 0%  -0.76% (p=0.002 n=6)
DemoSpeed/ld.yaml/Bundle-16          1.794M ± 0%   1.790M ± 0%  -0.22% (p=0.002 n=6)
DemoSpeed/plaid.yml/Index-16         308.4k ± 0%   304.3k ± 0%  -1.33% (p=0.002 n=6)
DemoSpeed/plaid.yml/Build-16         1.189M ± 0%   1.185M ± 0%  -0.35% (p=0.002 n=6)
DemoSpeed/plaid.yml/Render-16        939.8k ± 0%   939.8k ± 0%       ~ (p=0.924 n=6)
DemoSpeed/plaid.yml/Compare-16       2.912M ± 0%   2.876M ± 0%  -1.24% (p=0.002 n=6)
DemoSpeed/plaid.yml/Bundle-16        2.431M ± 0%   2.427M ± 0%  -0.17% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Index-16       558.6k ± 0%   556.8k ± 0%  -0.32% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Build-16       2.003M ± 0%   2.001M ± 0%  -0.09% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Render-16      2.130M ± 0%   2.130M ± 0%       ~ (p=1.000 n=6)
DemoSpeed/stripe.yaml/Compare-16     5.213M ± 0%   5.182M ± 0%  -0.58% (p=0.002 n=6)
DemoSpeed/stripe.yaml/Bundle-16      16.94M ± 0%   16.94M ± 0%  -0.01% (p=0.002 n=6)
geomean                              343.1k        341.2k       -0.54%

                                   │  base.txt  │             branch.txt             │
                                   │ changes/op │ changes/op  vs base                │
DemoSpeed/petstore.yaml/Compare-16   35.00 ± 0%   35.00 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/mistral.yaml/Compare-16    200.0 ± 0%   200.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/neon.yaml/Compare-16       205.0 ± 0%   205.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/ld.yaml/Compare-16         204.0 ± 0%   204.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/plaid.yml/Compare-16       500.0 ± 0%   500.0 ± 0%       ~ (p=1.000 n=6) ¹
DemoSpeed/stripe.yaml/Compare-16     500.0 ± 0%   500.0 ± 0%       ~ (p=1.000 n=6) ¹
geomean                              204.5        204.5       +0.00%
¹ all samples are equal

@daveshanley
daveshanley merged commit ae1153a into main Nov 1, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment