Skip to content

NaturalStringComparer builds a new Regex on every Compare call, so sorting large lists is slow and allocation-heavy #56

Closed

Description

@matt-edmondson

What's wrong

Sorting/NaturalStringComparer.cs:21 defines CreateNaturalChunkRegex() => new(@"(\d+)|(\D+)"). Compare calls it on every comparison (around line 55), then copies both match collections into arrays.

The SYSLIB1045 ("use GeneratedRegex") warning is suppressed with Justification = "<Pending>". That suggests the fix was intended and never made.

A sort makes O(n log n) comparisons. Each one pays for:

  • constructing and parsing a Regex
  • two MatchCollections plus their Match objects
  • two array copies

Measured impact

Release build, sorting 50,000 file names with List.Sort(new NaturalStringComparer()), about 857k comparisons:

Variant Time Allocated per compare
Current (new Regex per compare) 4.43 s ≈7.6 KB
Cached static Regex, nothing else changed 3.22 s ≈3.5 KB

A comparer that walks the chunks directly, with no Regex and no Match objects, should do much better again. The comparer is used for things like file lists and UI tables, where multi-second sorts are noticeable.

Suggested fix / acceptance criteria

  • At minimum, replace the per-call construction with a [GeneratedRegex] / static instance, and remove the <Pending> suppression.
  • Preferably, replace the Regex with a character scanner that walks both strings chunk by chunk using char.IsDigit. This keeps the current semantics, including the leading-zero and very-long-digit-run handling, and allocates nothing per compare.
  • Add a benchmark or a coarse perf test, and keep all existing ordering tests passing.
  • NaturalStringComparer is intransitive for non-ASCII digits (٥ < 10 < z < ٥), so sort output depends on input order #55 (non-ASCII digits) touches the same code; the two can be fixed together.

Activity

  1. matt-edmondson commented on Sep 27, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

readyFully specified; implement as written

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions