Skip to content

perf(pyutils): 2.5x faster natural_comparison_key (schema sort keys) - #311

Open
dexhunter wants to merge 1 commit into
graphql-python:mainfrom
dexhunter:perf/natural-comparison-key
Open

dexhunter wants to merge 1 commit into
graphql-python:mainfrom
dexhunter:perf/natural-comparison-key

Conversation

@dexhunter

@dexhunter dexhunter commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

  • This PR makes natural_comparison_key faster. Schema sorting, suggestions and value-node sorting use it.
  • Names with letters only return (key,) at once. Other names replace the digit parts in one slice.
  • Key benchmark for all GitHub schema names: 63.70 ms -> 25.62 ms, 59.8% faster.
  • Tests: PYTHONPATH=src python -m pytest tests/pyutils/test_natural_compare.py -q gives 5 passed.

Refs #310

What & Why

Before, the function split every name with a regex. Then it walked the parts with zip and itertools.cycle in a generator. Most GraphQL names, like userName, have no digits. For them, all this work gave the one-item tuple (name,).

Now the function checks key.isalpha() first and returns (key,) for such names. A name with letters only cannot contain a decimal digit, so the regex split would give the same tuple. Other names still use the regex split. The code replaces the digit parts at the odd positions in one slice assignment, without the generator. The type(key) is str check keeps the old result for str subclasses and the old error for other types.

Results

Time to compute the keys for each list of names (CPython 3.12, Linux, best of 7 timeit runs). The underscore and number rows skip the new letters-only check. They show that the check adds no slowdown.

Situation Before After Change
GitHub schema names (3323 names) 2.11 ms 0.34 ms 84% faster
Letters only, userName (5000 names) 3.00 ms 0.38 ms 88% faster
Underscore names, USER_NAME (5000 names) 3.09 ms 1.83 ms 41% faster
Digits only, 42 (5000 names) 3.69 ms 2.18 ms 41% faster
Names with numbers, field12x3 (5000 names) 5.61 ms 3.72 ms 34% faster
Full lexicographic_sort_schema on the GitHub schema 17.9 ms 15.2 ms 15% faster

Why this is safe and easy to maintain

The change is six short lines in one function and removes the itertools import. The keys are exactly the same as before, including Unicode digits. The existing tests in tests/pyutils/test_natural_compare.py and tests/utilities pass without changes.

Validation

  • PYTHONPATH=src python -m pytest tests/pyutils/test_natural_compare.py -q: 5 passed.
  • PYTHONPATH=src python -m pytest tests/utilities -q: 663 passed, 16 skipped.
  • An automated check compared the keys of the old and new code for many generated names. All keys were equal.
  • Benchmark command:
PYTHONPATH=src python -m timeit -s "from pathlib import Path; from graphql import build_schema; from graphql.utilities import lexicographic_sort_schema; s = build_schema(Path('tests/fixtures/github_schema.graphql').read_text())" "lexicographic_sort_schema(s)"

AI assistance

This PR was prepared with AI assistance from Aiden. Candidates were tracked with Weco Observe.

Weco share link: https://dashboard.weco.ai/share/_QqKTzEBeaYlxdM2E1ve-t47SvlL9Iub

Return (key,) at once for names with letters only, which cannot
contain a decimal digit. For other names, replace the digit parts of
the regex split in one slice assignment instead of walking the parts
with zip and itertools.cycle. The keys stay exactly the same.

Sort keys for all GitHub schema names: 63.70 ms -> 25.62 ms.

Co-Authored-By: Aiden <aiden@weco.ai>
@dexhunter
dexhunter requested a review from Cito as a code owner November 7, 2026 21:32
@codspeed

codspeed Bot commented Nov 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 20 untouched benchmarks


Comparing dexhunter:perf/natural-comparison-key (493a752) with main (1d505c1)

Open in CodSpeed

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