GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — perf(values): depth check keeps one frame per level -- 80% less memory by dexhunter · Pull Request #318 · graphql-python/graphql-core

Conversation

@dexhunter

@dexhunter dexhunter commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

TL;DR

  • Lower the peak memory of input_is_nested_too_deeply, the depth check for variable values.
  • Keep one stack frame per nesting level and do not push one tuple per list item.
  • tracemalloc peak for a [Int] variable of 50,000 ints: 4,091,392 bytes -> 818,981 bytes, 80.0% less.
  • Native tests: PYTHONPATH=src python3 -m pytest tests/execution/test_variables.py -q passed 106 tests.

Refs #317

What & Why

Before, the check pushed one (item, type, depth) tuple per list item onto its stack before it popped any.
The memory for the check grew with the width of the list, about 70 bytes per item.

Now each stack frame holds one nesting level: the values, their type, the depth, and the next index.
The check also stops the walk of a level when the item type is not a list or an input object.
Such items cannot nest, so one depth check covers the full level.

The output stays identical because the check still visits each followed value at the same depth.
Empty lists still add no depth, and values under scalars are still not followed.

Validation

Peak traced memory (tracemalloc) and best time of one call to the check:

Situation Before After Change
[Int], 50,000 ints 3,516,880 bytes, 11.50 ms 112 bytes, 0.0005 ms memory no longer grows with the list
[Int], 10,000 ints 597,680 bytes, 2.16 ms 112 bytes, 0.0005 ms memory no longer grows with the list
[[Int]], 500 lists of 100 ints 5,936 bytes, 10.58 ms 176 bytes, 0.23 ms about 46x less time
[Point], 5,000 input objects 234,608 bytes, 8.59 ms 712 bytes, 7.25 ms 16% less time
Int, one value 128 bytes, 0.0003 ms 88 bytes, 0.0003 ms no measurable change in time

The last row shows a case where the change does not help, and it shows no slowdown.
The number in the TL;DR is for the full get_variable_values call, so it includes the coerced result.
Measured on CPython 3.12.13, Linux x86-64, AMD EPYC 9R14, on one isolated core.

Script for the first row; run it on the base commit and on this change:

import tracemalloc
from graphql import GraphQLInt, GraphQLList
from graphql.execution.values import input_is_nested_too_deeply

value = list(range(50_000))
tracemalloc.start()
input_is_nested_too_deeply(value, GraphQLList(GraphQLInt))
print(tracemalloc.get_traced_memory()[1])

The complete suite also passed with PYTHONPATH=src python3 -m pytest tests -q: 3,821 passed, 18 skipped.
ruff check, ruff format --check (ruff 0.17.4) and mypy (2.4.1) passed for src/graphql/execution/values.py.

Why this is safe and easy to maintain

The change is small and easy to read: 13 added lines in one private function, with no new imports.
The function keeps its loop, its explicit stack, and its signature.
Behaviour is unchanged, and the returned bool is the same for every value and type.
The tests in tests/execution/test_variables.py cover the depth limit for lists, input objects, and custom scalars.

Scope

This changes Python bookkeeping in a private helper only.
It adds no feature and does not change GraphQL behavior or the public API.

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/yE6YtGpMB4NV3RCoYfCM3tHnAk6srhv_

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@dexhunter @aiden-weco
perf(values): depth check keeps one frame per level -- 80% less memory
431b46e
input_is_nested_too_deeply pushed one tuple per list item onto its stack
before it popped any, so its memory grew with the width of the list.
Keep one frame per nesting level (values, type, depth, next index) and
stop the walk of a level when its item type cannot nest.

Coercing a [Int] variable of 50,000 ints has a tracemalloc peak of
818,981 bytes, down from 4,091,392 bytes. The returned bool is unchanged.

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

codspeed Bot commented Nov 10, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 20 untouched benchmarks


Comparing dexhunter:perf/nested-input-depth-check-memory (0259ce7) with main (1d505c1)

Open in CodSpeed
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

dexhunter and others added 2 commits November 10, 2026 20:27
@dexhunter @aiden-weco
test(values): cover wide and empty lists in the nesting depth check
290202b
The depth check keeps one frame per nesting level. No test used a list
with several nested items or an empty list, so one line and one branch
were not covered and the 100% coverage gate failed on Python 3.14.

Co-Authored-By: Aiden <[email protected]>
@dexhunter @aiden-weco
test(values): fold wide and empty list checks into one depth test
0259ce7
Reuse the deep list type of the existing test instead of building it
again in a second test.

Co-Authored-By: Aiden <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@Cito Cito Awaiting requested review from Cito Cito is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@dexhunter
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.