Skip to content

chore(deps): go-python-packaging v0.6.0, for the packed comparison key - #41

Merged
jonyoder merged 3 commits into
mainfrom
chore/gpp-0.6.0
Aug 14, 2026
Merged

chore(deps): go-python-packaging v0.6.0, for the packed comparison key#41
jonyoder merged 3 commits into
mainfrom
chore/gpp-0.6.0

Conversation

@jonyoder

@jonyoder jonyoder commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Bumps github.com/posit-dev/go-python-packaging from v0.5.0 to v0.6.0. No code in this repository changes — the diff is go.mod, go.sum and a CHANGELOG entry.

v0.6.0 compares versions through a packed 4×uint64 order-preserving key rather than a field-by-field walk, for the 97.3% of real versions that fit one.

Measured

Medians of five interleaved rounds, ten iterations, against the production snapshot (932,861 packages, dated 2026-08-04), Apple M4 Max. Interleaved round by round so ambient load lands on both sides.

entry cold before cold after warm before warm after
single-no-deps 1.18 ms 0.24 ms 4.9x 0.24 ms 0.17 ms 1.42x
small-tree 4.87 ms 1.21 ms 4.0x 1.22 ms 0.91 ms 1.34x
extras 6.47 ms 1.65 ms 3.9x 1.71 ms 1.32 ms 1.29x
app-set 20.67 ms 5.50 ms 3.8x 5.40 ms 4.22 ms 1.28x
wide-versions 167.35 ms 12.46 ms 13.4x 14.15 ms 7.61 ms 1.86x
backtracking 11.16 ms 5.61 ms 2.0x 3.10 ms 2.59 ms 1.20x
unsatisfiable 2.33 ms 0.47 ms 4.9x 0.45 ms 0.30 ms 1.50x

Cold gains far exceed warm, and that asymmetry is the finding. Building a package's sorted version order is comparison-bound and happens once per package per index, so it lands entirely in cold. Warm reuses that order through 0.5.0's memo, so the packed key only reaches the residual comparisons inside pep440set containment and candidate filtering.

The interaction with #40 is measured, not asserted

The two contend — both attack version-comparison cost, so each is worth less once the other has landed. 2×2, one interleaved session, warm, medians of three:

entry packed gain BEFORE #40 packed gain AFTER #40 #40 gain at v0.5.0 #40 gain at v0.6.0
single-no-deps 5.04x 1.42x 6.14x 1.73x
small-tree 2.37x 1.32x 3.26x 1.81x
extras 3.10x 1.30x 5.44x 2.28x
app-set 5.33x 1.28x 12.34x 2.95x
wide-versions 5.93x 1.88x 5.42x 1.72x
backtracking 4.01x 1.23x 7.49x 2.29x
unsatisfiable 1.99x 1.48x 1.43x 1.07x

On app-set, multiplying the isolated headlines predicts 12.34 × 5.33 = 65.8x; measured end-to-end from 73d820a+v0.5.0 to 276fe91+v0.6.0 is 15.7x. ⚠️ Opposite sign to the #39/#40 interaction, where Contains became more valuable after the rank memo — so these interactions cannot be guessed.

This also resolves an apparent conflict with go-python-packaging's own release notes, which claim 2.1–5.2x warm on this benchmark: those were measured at 73d820a, before #40 landed 46 minutes later. The pre-#40 column reproduces them (1.99–5.93x).

Not moved: pypirsf.Open + NewRSFIndex is flat at ~220 ms (220.0 → 216.6, within noise) — it builds a name-to-offset table and parses no versions.

Peak heap is flat to better (wide-versions 18.7 → 12.3 MB over baseline), with one exception kept rather than dropped: backtracking retains slightly more (6.0 → 6.5 MB).

Correctness

candvers, metadata and the pin set are identical on every corpus entry — this changes what a comparison costs, not what it answers.

Cross-build equivalence, which is the check that matters for a comparison-key change: 4,007 resolutions against the production snapshot (the 7 corpus entries plus 4,000 sampled package names, seed 1) produce byte-identical transcripts before and after — identical pins, identical decision order, identical activated extras, and identical failure report text on the 1,607 that fail. 36 cases that hit an 8-second wall-clock deadline are excluded (35 both sides, 1 baseline only, 0 bumped only).

Suite: go test ./... and go test ./... -race both green against the production snapshot at GPR_SAMPLE=20000.

⚠️ Note for reviewers: unset, provider's and contiguity's differentials walk all 932,861 packages ("default 0 means all of them"), which is a multi-hour ~10 GB run. At GPR_SAMPLE=2000 TestCandidatesAgreeWithAnExactCountOnTheRealIndex fails its own anti-vacuity guard — identically on v0.5.0 and v0.6.0 (same 2000/1440/3/0 counts), so it is sample size, not this bump.

🤖 Generated with Claude Code

jonyoder and others added 3 commits August 14, 2026 10:23
Bumps the dependency only; no code in this repository changes.

v0.6.0 compares versions through a packed 4xuint64 order-preserving key
rather than a field-by-field walk, for the 97.3% of real versions that
fit one. Against the production snapshot (932,861 packages), medians of
five interleaved rounds: cold resolution is 2.0x to 13.4x faster and
warm 1.2x to 1.9x.

The asymmetry is the point. Building a package's sorted version order is
comparison-bound and happens once per package per index, so it lands in
cold -- wide-versions (botocore, >10k releases) drops 167.35 ms to
12.46 ms. Warm reuses that order through 0.5.0's memo, so the packed key
only reaches the residual comparisons inside pep440set containment and
candidate filtering. 1.2-1.9x warm says those were still material after
 #39 and #40; that it is not larger says #40 had already removed most.

This does not compose multiplicatively with #39 or #40 and was not
assumed to: #40 removed most comparisons and the packed key makes the
remainder cheap, so they contend for the same cost. Measured against
276fe91, which contains both.

candvers, metadata and the pin set are identical on every corpus entry.
Equivalence checked across builds: 4,007 resolutions against the
production snapshot (7 corpus entries plus 4,000 sampled names, seed 1)
produce byte-identical transcripts -- identical pins, decision order,
activated extras, and failure report text on the 1,607 that fail -- with
36 wall-clock-deadline cases excluded (35 both sides, 1 baseline only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bump entry asserted the packed key and #40 do not compose
multiplicatively. That was reasoning, not a measurement. This records the
2x2 that establishes it, in one interleaved session, warm, medians of 3.

The packed key is worth 1.99-5.93x BEFORE #40 and 1.23-1.88x after; #40 is
worth 1.43-12.34x at gpp v0.5.0 and 1.07-2.95x at v0.6.0. Both directions
shrink, so the two are substitutes: on app-set, multiplying the isolated
headlines predicts 65.8x against a measured 15.7x end to end.

That is the opposite sign to the #39/#40 interaction, where Contains became
MORE valuable after the rank memo, so the interaction cannot be guessed.

It also resolves an apparent conflict with go-python-packaging's own release
notes, which claim 2.1-5.2x warm on this benchmark: those were measured at
73d820a, before #40 landed 46 minutes later. The pre-#40 column reproduces
them (1.99-5.93x).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by review, not by me: go-python-packaging v0.6.0 also lands, under
its own Fixed heading, "a parsed Version is now safe to share across
goroutines" -- Compare pads into a fresh slice instead of appending into
spare capacity a by-value copy shares.

That restriction was documented on an EXPORTED API. PackageMetadata.
SupportsPython told callers "DO NOT SHARE ONE PARSED target BETWEEN
GOROUTINES ... give each goroutine its own version.Parse", and closed with
"a fix is being filed upstream; until it lands there is nothing this method
can do about it". This bump IS that fix landing, so the guidance now asks
callers to do unnecessary work.

Corrects that doc and the canonical account in RSFIndex.Versions, which
also carried the trigger "when it is fixed upstream, memoizing the parsed
values becomes available". The historical explanation is kept in both:
it is still why several types memoize version KEYS rather than parsed
values, and those choices remain correct -- they are now a choice rather
than a requirement.

Re-verified against both pins with the exact eight-goroutine repro the doc
specifies (target 3.11.0, constraint >3.9.1): v0.5.0 reports WARNING: DATA
RACE, v0.6.0 is clean.

No behaviour changes. Taking the now-unblocked parsed-version memo is a
performance change owing its own measurement and is left to a follow-up,
along with the remaining internal rationales in provider, index/mock.go
and resolver/bench_test.go.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jonyoder
jonyoder merged commit 75b0ed8 into main Aug 14, 2026
2 checks passed
@jonyoder
jonyoder deleted the chore/gpp-0.6.0 branch August 14, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant