Skip to content

perf(coerce_input_literal): 35% faster Int! literals, 4% on args bench - #303

Open
dexhunter wants to merge 1 commit into
graphql-python:mainfrom
dexhunter:perf/coerce-input-literal-direct-type-checks
Open

dexhunter wants to merge 1 commit into
graphql-python:mainfrom
dexhunter:perf/coerce-input-literal-direct-type-checks

Conversation

@dexhunter

Copy link
Copy Markdown
Contributor

TL;DR

  • This PR makes coerce_input_literal faster. This function coerces literal arguments such as multiplier: 2 during execution.
  • It unwraps NonNull in place and uses direct isinstance checks instead of the is_*_type helpers.
  • The execution-arguments benchmark goes from 300.24 ms to 287.30 ms (4.3% faster, median of 3 runs).
  • The full test suite passes: 3625 passed, 18 skipped, the same as on main.

Refs #302

What & Why

Before, a NonNull type made the function call itself with the inner type. That second call checked the variable node and the null literal again. Each type check also went through an is_*_type helper, which is one more function call around isinstance.

Now the function checks for a null literal once. Then it replaces type_ with type_.of_type and continues in the same call. It checks GraphQLNonNull, GraphQLList and GraphQLInputObjectType directly. The list and object loops keep append and get in local names. The object branch reads each field value node once. Leaf types still go through assert_leaf_type. The branches return the same values in the same order, so every result stays identical.

graphql-js uses recursion for NonNull in coerceInputLiteral. This PR changes that on purpose, because a Python function call costs much more than a JavaScript call.

Results

Time per call, median of 3 runs on Python 3.12:

Situation Before After Change
Int! literal (multiplier: 2) 734 ns 479 ns 35% faster
[Int!] list of 10 ints 7557 ns 5089 ns 33% faster
input object {a: 1, b: "x"} 2355 ns 1717 ns 27% faster
variable $v (code path not changed) 172 ns 156 ns no real change (runs vary from 156 to 193 ns)
execute_sync of the 1000-item query from test_execution_arguments.py 5.32 ms 5.00 ms 6% faster

Why this is safe and easy to maintain

The change is small and stays inside one function. It uses the same isinstance checks that the is_*_type helpers use, so behaviour and output do not change. The existing tests in tests/utilities/test_coerce_input_value.py, tests/execution and the module doctests cover all branches.

Validation

  • Tests: pytest tests gives 3625 passed, 18 skipped, with and without this change. mypy and ruff check/ruff format --check pass on the changed file.
  • Benchmark: a workload with the 1000-item query from tests/benchmarks/test_execution_arguments.py plus 336 mixed literal cases. It runs in 300.24 ms before and 287.30 ms after (median of 3 runs, each 11 alternating before/after pairs).
  • Quick check with the repository benchmark: pytest tests/benchmarks/test_execution_arguments.py --benchmark-only

I did not add new tests because behaviour does not change.

AI assistance

Candidate changes came from Codex (gpt-6.1-sol), and Weco Observe tracked them. Claude Code picked this candidate, checked it with the test suite, mypy and ruff, and measured it. A person did not review the change before submission.

Weco share link: https://dashboard.weco.ai/share/z3O_QHq3doOpQrFvfclgdtSNB6ioeuCH

Unwrap NonNull in place instead of calling coerce_input_literal again,
and use direct isinstance checks instead of the is_*_type helpers.
Check the null literal once, and keep append/get and the field value
node in local names in the list and object loops. Leaf types still go
through assert_leaf_type.

The execution-arguments benchmark goes from 300.24 ms to 287.30 ms
(median of 3 runs). An Int! literal goes from 734 ns to 479 ns.

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

codspeed Bot commented Nov 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 20 untouched benchmarks


Comparing dexhunter:perf/coerce-input-literal-direct-type-checks (c0bb1a4) 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