Skip to content

fix: parse each version once in sort and rsort - #948

Open
TheNetherWatcher wants to merge 1 commit into
npm:mainfrom
TheNetherWatcher:perf/sort-parse-once
Open

TheNetherWatcher wants to merge 1 commit into
npm:mainfrom
TheNetherWatcher:perf/sort-parse-once

Conversation

@TheNetherWatcher

Copy link
Copy Markdown

sort and rsort use compareBuild as the comparator, and compareBuild builds two SemVer objects every time it is called. That runs the full regex parse about 2·n·log n times for a list of n versions.

This change keeps a small per-call Map from each list element to its parsed SemVer, so each element is parsed once. The comparator gives the same results and is called in the same order, so the output, stability, in-place mutation and any thrown error stay exactly the same. That includes the edge cases: one-element lists aren't validated, undefined entries still go to the end, and with several invalid versions the error still names the same one.

Context: #814, where it was suggested that perf improvements be sent as PRs.

Benchmark

Random lists (about 30% prereleases, 10% build metadata), Node 24, ms per call:

n before after speedup
10 0.008 0.002 3.4x
50 0.071 0.013 5.4x
500 1.239 0.170 7.3x
5000 18.309 3.128 5.9x

rsort numbers are the same.

Equivalence

I compared the old and new sort/rsort on 120k random lists. The lists mixed valid strings, v/=-prefixed and padded strings, build metadata, SemVer instances (some with mismatched options), invalid strings, numbers, undefined and holes, and each run used one of the option values undefined, true, false, { loose }, { includePrerelease } or both. Both versions returned the same element references in the same order, mutated the input the same way, and threw the same error type and message.

Tests

Added tests for in-place return, stability of equal versions, SemVer instance inputs, and loose (including that invalid input throws TypeError). npm test passes with 100% coverage, lint and template-oss-check.

The comparator used compareBuild, which constructs two SemVer objects
on every comparison. Cache the parsed version per element so each one is
parsed once. Comparator results and call order are unchanged, so output,
stability, in-place mutation and thrown errors are identical.
@TheNetherWatcher
TheNetherWatcher requested a review from a team as a code owner November 6, 2026 16:45
@jeffkl
jeffkl requested a balanced review from Copilot November 6, 2026 21:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues are identified for the sorting cache changes.

No open issues 🎉

What changed in this PR

Optimizes sort and rsort by caching parsed versions within each call, addressing the sorting performance opportunity described in #814.

Changes:

  • Reuses parsed SemVer objects instead of reconstructing them on every comparison.
  • Retains lazy parsing and the existing comparison order for ascending and descending sorts.
  • Adds tests for in-place mutation, stable ordering, instance identity, loose parsing, and invalid input.
File Description
test/​functions/​sort.js Adds ascending-sort compatibility tests.
test/​functions/​rsort.js Adds descending-sort compatibility tests.
functions/​sort.js Caches parsed versions during ascending sorting.
functions/​rsort.js Caches parsed versions during descending sorting.

Review effort: Balanced

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.

2 participants