Skip to content

perf(schema): reduce type checks -- 31% less rebuild time - #301

Closed
dexhunter wants to merge 1 commit into
graphql-python:mainfrom
dexhunter:perf/schema-type-checks
Closed

dexhunter wants to merge 1 commit into
graphql-python:mainfrom
dexhunter:perf/schema-type-checks

Conversation

@dexhunter

Copy link
Copy Markdown
Contributor

TL;DR

  • Speed up TypeSet.collect_referenced_types when rebuilding a schema.
  • Use direct class checks and skip work for types already collected.
  • GitHub schema recreation benchmark: 1.310 ms → 0.903 ms, using 31.0% less time.
  • Tests: PYTHONPATH=src python -m pytest -q: 3,625 passed, 18 skipped.

Refs #300

What & Why

Schema construction repeatedly called wrapper and type-check helpers, including for references already collected.

The traversal now uses direct checks and skips repeated work. It keeps the same type order, lazy lookups, and errors.

Validation

Median times on CPython 3.12.13, Linux x86-64, measured on the same isolated CPU:

Situation Before After Change
Recreate the GitHub schema 1.310 ms 0.903 ms 31.0% less time
Build a schema from introspection 14.959 ms 14.713 ms 1.6% less time
Execute an async iterable list field 1.212 ms 1.199 ms 1.1% less time
Decode a pickled large-query AST 6.955 ms 6.957 ms No measurable change

The gain applies to schema recreation. The other workloads show little change.

The seven tests in the serialization, schema, async iterable, and client-schema benchmark modules also pass.
Ruff lint and formatting checks pass for src and tests; mypy passes for all 152 source files.

To reproduce the native checks and these benchmarks:

PYTHONPATH=src python -m pytest -q
PYTHONPATH=src python -m pytest tests/benchmarks/test_graphql_schema.py \
  tests/benchmarks/test_build_client_schema.py \
  tests/benchmarks/test_async_iterable.py \
  tests/benchmarks/test_serialization.py --benchmark-enable

Why this is safe and easy to maintain

The change stays within one traversal method and its imports.
Existing tests cover schema type collection, wrappers, cycles, and schema errors.
The results retain the same type identities and insertion order.

Implementation details

The cast is used only during static type checks. Recursive method bindings are created only in branches that traverse references.
Empty built-in argument dictionaries skip iteration; custom mappings keep their original traversal path.

AI assistance: Codex gpt-6.1-sol generated candidates; Weco Observe recorded their results.

Weco share link: https://dashboard.weco.ai/share/9qoGWp7nl9WycSEh72uDDBGedk8a-V6N

Use direct class checks in TypeSet.collect_referenced_types and keep
the named-type cast for static type checking only. Bind recursive calls
only for types with references and skip empty built-in argument maps.

Preserve traversal order, type identity, lazy lookups, and custom mappings.
The native suite, seven benchmark tests, Ruff, and mypy pass.

Co-Authored-By: Aiden <[email protected]>
@dexhunter
dexhunter requested a review from Cito as a code owner November 6, 2026 17:00
@codspeed

codspeed Bot commented Nov 6, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 77.43%

1 warning

⚠️ 1 benchmark with a significant change ran on different hardware

Its base and head results were measured on machines with a different CPU, core count or memory, so the change might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

⚡ 1 improved benchmark
✅ 19 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_recreate_a_graphql_schema 30.1 ms 17 ms +77.43%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing dexhunter:perf/schema-type-checks (866345f) with main (0aaea71)

Open in CodSpeed

@Cito

Cito commented Nov 6, 2026

Copy link
Copy Markdown
Member

Thanks! I applied this as 7716d115, with you as the author, so GitHub shows this PR as closed rather than merged. The commit is signed and sits on top of the release branch, which had to stay linear.

Randomized schemas gave results identical to main, including the error cases. On Python 3.14 I measured about 23% less time for recreating the GitHub schema and about 2% for building a client schema.

I left out the type(args) is dict and not args guard, since it added little to the gain and this keeps the loop simple.

This is part of 3.3.2.

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