Remove allocations from ALPM, Conan and Gentoo comparators - #42
Conversation
andrew
left a comment
There was a problem hiding this comment.
The new BenchmarkCompareEcosystem duplicates the harness in BenchmarkCompareWithScheme, so the extended golangci-lint check fails with dupl. Please add the ecosystem cases to the existing benchmark table or extract the shared runner.
There was a problem hiding this comment.
Pull request overview
This PR removes heap allocations from the remaining ecosystem-specific version comparators (ALPM, Conan, Gentoo/APK) by switching from split/slice-based parsing to allocation-free, lockstep scanning over string views, while preserving comparator behavior via differential parity tests.
Changes:
- Reworks ALPM, Conan, and Gentoo/APK comparison to iterate over runs/components directly (no
strings.Split, no slice materialization). - Generalizes the shared dot-splitting helper (
nextSemverIdentifier→nextDotPart) and reuses it in the ecosystem comparators. - Adds reference implementations plus parity + zero-allocation tests, and introduces a benchmark to keep these comparators covered by
-benchmem.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| schemes.go | Renames/generalizes the dot-part iterator helper for reuse outside SemVer. |
| ecosystem_extra.go | Rewrites ALPM/Conan/Gentoo comparison logic to be allocation-free via scanning/string views. |
| ecosystem_extra_reference_test.go | Adds mechanically-copied pre-optimization reference implementations for differential testing. |
| ecosystem_extra_parity_test.go | Adds exhaustive parity checks across corpora + AllocsPerRun tests to pin 0 allocations. |
| bench_test.go | Adds an ecosystem-specific benchmark to keep these comparators visible in perf/alloc regressions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Thanks, fixed. I went with folding the cases into the existing table rather than extracting a runner, so the duplication is removed instead of restructured around. Since you mentioned the extended check, I also diffed the full linter-set profile of this branch against |
Part of #31. Closes three of the seven checklist boxes: ALPM, Conan and Gentoo/APK.
Approach
Same scanner plus stack-buffer treatment #30 applied to SemVer, RubyGems and NuGet, extended to the three comparators that live in
ecosystem_extra.go. Each one now walks both versions in lockstep through string views instead of materializing a component list per side.splitTypedSegmentsgrew a[]typedSegmentby append, four times per comparisonnextTypedSegment, a lockstep iterator over runs of same-kind bytesstrings.Splitplus a recursive parse whose&preand&buildpointers escaped to the heapsplitConanVersionreturns views; the recursion runs over those views, costing stack frames rather than allocationsstrings.Splitcalls per comparisonsplitGentooBase,nextGentooSuffixandnextDotPartConan's trailing numeric-zero rule, where
1.2.0and1.2are equal, is handled by counting significant components withconanMainLeninstead of building a slice and truncating it.One shared change in
schemes.go:nextSemverIdentifieris plain dot-splitting with nothing SemVer-specific in it, so it is renamednextDotPartand reused by the Gentoo and Conan paths rather than copied. No behavior change, just the two call sites.Exported APIs and scheme ordering are unchanged.
Results
Measured with Go 1.26 on darwin/arm64. The before column comes from running the identical new benchmark file against
mainin a throwaway worktree, so the two runs are directly comparable.Between 2.3x and 4.1x faster, with every allocation removed.
BenchmarkCompareEcosystemis new inbench_test.goso these paths stay covered, sinceBenchmarkCompareWithSchemedid not reach any of them.Parity coverage
The issue asks for differential coverage against current behavior, so
ecosystem_extra_reference_test.goholds the pre-optimization implementations underref*names. It was produced by a rename script rather than retyped, so it is a faithful copy of what was onmain.ecosystem_extra_parity_test.gothen asserts the rewrite returns an identical result for every ordered pair across two corpora:A
testing.AllocsPerRuntest pins the zero-allocation property so a future change cannot quietly reintroduce a heap escape.Mutation testing
I checked that the parity suite actually bites rather than assuming it. Each of these deliberate breakages produced a failure naming the exact offending input:
compareConan("", "0")compareConan("1", "1.0+build-alpha")_as having no suffixescompareGentoo("", "_")_instead of the firstcompareGentoo("1.2.3", "1.2.3_alpha_p1")compareALPM("1.0", "1a.2b")One mutation survived, changing the Gentoo no-letter sentinel from
-1to0. That one is an equivalent mutant rather than a coverage gap: every ASCII letter exceeds both values, socmpIntreturns the same answer either way.Testing
gofmt -l .clean,go build ./...andgo vet ./...passgo test -race -count=1 ./...passes on all three packages, conformance suite includedgolangci-lint run ./...reports 0 issues