Reduce Execution::Next lookup and step overhead - #5696
Open
ydah wants to merge 3 commits into
Open
Conversation
ydah
force-pushed
the
perf/execution-next-selected-optimizations
branch
from
August 11, 2026 12:25
bb3fc51 to
ca09137
Compare
ydah
force-pushed
the
perf/execution-next-selected-optimizations
branch
from
August 11, 2026 12:26
ca09137 to
5b161ce
Compare
Owner
|
Hey, thanks so much for tracking down these improvements and sharing your results. This looks like a great win. I thought I had already avoided PrepareObjectStep whenever I could but I must have missed a spot! I have a couple other big issues to review at the moment but I'll take a close look at this one soon. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
This PR reduces three input-size-dependent costs in
GraphQL::Execution::Nextwithout adding or changing public APIs:PrepareObjectStepallocations when no preparation is requiredLoadArgumentStepallocations for list arguments usingloads:Each optimization is kept in a separate commit and includes coverage for the compatibility-sensitive paths it changes.
Changes
ba4c954 : Index large execution argument nodes
Large argument collections previously performed a linear AST scan for every argument definition, making argument lookup quadratic as the number of arguments increased.
This commit builds a name index when there are at least 32 argument nodes. Smaller collections keep the existing linear lookup to avoid paying for a Hash allocation on common queries.
The index uses
||=so invalid documents executed withvalidate: falsepreserve the existing behavior of selecting the first duplicate argument.ae5dc8d : Skip unnecessary object preparation steps
Concrete, eager composite values previously allocated one
PrepareObjectStepper returned object, even when no authorization, type resolution, scoping, post-processing, or finalization was required.This commit adds a conservative direct-result path for eager concrete objects. The existing preparation path remains in use for:
The direct path preserves authorization trace events expected by schemas configured with lazy resolution.
bb3fc51 : Batch list argument loading steps
A list argument using
loads:previously allocated oneLoadArgumentStepfor every list item.This commit replaces those per-item step objects with one
LoadArgumentsStepstate machine. Public loading and authorization hooks are still called once per item and retain their existing order.The batch step preserves:
object_loadedtrace orderGraphQL::Current.fieldduring trace hooksrescue_fromhandling for loading and trace hook failuresScalar
loads:arguments continue using the existingLoadArgumentStep.Benchmarks
Environment
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISMThe benchmark uses pre-parsed documents with
validate: falseto isolate execution work. Each timing is the median of five samples after warmup. Before timing, the benchmark verifies that the legacy andExecution::Nextresults match.Allocation totals were collected with
MemoryProfiler.Argument node indexing
The index intentionally trades a small allocation increase for eliminating the quadratic lookup cost. A zero-argument query changed from 59.41 µs to 59.90 µs (+0.8%), within measurement noise.
Skipping object preparation
This workload returns 1,000 eager concrete objects with 25 scalar fields each.
Authorization and abstract-type fallback workloads continue using
PrepareObjectStep. Repeated measurements of those fallback paths were within 2% of the baseline and had unchanged allocation counts.Batched list argument loads
For 1,000 loaded values:
A follow-up alternating-process run after the error-handling hardening showed the same allocation counts. Runtime varied by less than approximately 1% compared with the pre-hardening implementation.