Skip to content

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

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

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

Metadata

Metadata

Assignees

No one assigned

    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