Skip to content

fix: 1.3–1.6x faster satisfies() with includePrerelease by not re-parsing comparator versions - #923

Open
deadstrobe5 wants to merge 1 commit into
npm:mainfrom
deadstrobe5:fix/comparator-semver-options
Open

deadstrobe5 wants to merge 1 commit into
npm:mainfrom
deadstrobe5:fix/comparator-semver-options

Conversation

@deadstrobe5

Copy link
Copy Markdown

satisfies() / maxSatisfying() with { includePrerelease: true } get 1.3–1.6x faster, with identical results.

Why

Comparator#parse creates its SemVer with this.options.loose instead of this.options, so the comparator's version always has includePrerelease: false. When a Range is built with { includePrerelease: true }, Comparator#test → cmp → compare calls new SemVer(this.semver, options); the includePrerelease flags don't match, so the version string is re-parsed with the FULL regex on every test call. In a CPU profile, SemVer construction was ~24% and the version regex ~8% of satisfies time.

Passing the full options makes the instance reusable. Parsing only depends on loose, which is unchanged, so results are identical. The only visible difference is that comparator.semver.includePrerelease / .options now match the comparator's own options.

Benchmark

Benchmark.js, median of 5 alternating runs per side, Node 26, Apple M5, { includePrerelease: true }:

Case Before (ops/s) After (ops/s) Speedup
satisfies(1.0.6, '1.0.3||^2.0.0') 2.01M 2.68M 1.33x
satisfies(2.2.3, '1.0.3||^2.0.0') 1.43M 2.24M 1.57x
satisfies(3.0.1-beta.1, '2.2.2||~3.0.0') 1.18M 1.72M 1.45x
maxSatisfying(20 versions, '^1.0.0') 0.10M 0.15M 1.47x

On a corpus of 2,000 versions × 300 ranges: satisfies ~25% faster, maxSatisfying ~41% faster. Without includePrerelease: no change.

(The existing benchmarks/bench-satisfies.js spells the option includePrelease, so it never hits this path; happy to fix that separately.)

Tests

  • Added a test in test/classes/comparator.js that the comparator's semver carries the comparator options.
  • npm test passes with 100% coverage; eslint clean.
  • Differential run of ~390k cases against the previous code (satisfies, maxSatisfying/minSatisfying, minVersion, validRange, intersects, subset, gtr/ltr/outside, simplifyRange, toComparators, and Range/Comparator#test with SemVer objects built under every loose/includePrerelease combination, plus leading zeros, v/= prefixes, build metadata and huge numbers): 0 differences, including error messages.

Thanks for semver!

@deadstrobe5
deadstrobe5 requested a review from a team as a code owner October 16, 2026 05:08
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