Skip to content

Validate the params of an optional Hash scope without the iterator - #3075

Merged
ericproulx merged 1 commit into
masterfrom
perf/direct-optional-hash-scopes
Nov 11, 2026
Merged

ericproulx merged 1 commit into
masterfrom
perf/direct-optional-hash-scopes

Conversation

@ericproulx

@ericproulx ericproulx commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

A validator reads its scope's params directly, without the attributes iterator, when its scope is always validated: the root scope and the required Hash scopes under it (#2936). A Hash scope that is optional, or that sits under an optional one, still went through the iterator, although the iterator has nothing to add there either. #validate only goes on once the scope's params are there and not blank, so the iterator hands that one Hash back, with no empty value to pass over and no dependency to meet.

The direct path now covers every scope with no Array scope and no given on its chain:

@direct = scope.independent? && scope.array_depth.zero?

It also stops trying a required Hash scope inside an Array scope. The params of such a scope resolve to an Array, so the direct path never applied to them, and the iterator then resolved them a second time.

A scope under a given stays on the iterator, so its dependency is asked as often as it is on master.

Numbers

Interleaved A/B from one checkout, median of per-round pairs over 6 rounds. In parentheses, the rounds the branch won.

request interpreter YJIT
POST, 9 params in three optional Hashes, one inside another +17.5% (6/6) +8.1% (6/6)
POST, an Array of five Hashes, each requiring a Hash +6.7% (6/6) +7.8% (6/6)
PUT by id, 9 params, one an optional Hash +6.5% (6/6) +2.0% (5/6)
POST, 23 params declared with three optional Hashes and an Array, 4 of the 15 top-level ones sent +5.5% (6/6) +2.9% (4/6)
POST, 10 params with an optional Hash and an Array of three Hashes +2.2% (6/6) +0.2% (3/6)
GET, four query params, root scope only (control) -0.5% +2.0%
route with no params (control) +0.2% +0.3%

Checked against master

About 88,000 requests over random declarations (Hash and Array scopes, nested, required and optional, under given, with defaults and values) and random payloads, a quarter of them valid: the same status and the same error messages. Taking the direct path for every scope makes 267 of one seed's 10,140 requests answer differently, so the comparison does tell a wrong path apart.

No contract change, so no UPGRADING entry.

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the perf/direct-optional-hash-scopes branch 2 times, most recently from 5ac65e1 to 8aa2778 Compare November 10, 2026 10:52
A validator reads its scope's params directly, and skips the attributes
iterator, when its scope is always validated: the root scope and the
required Hash scopes under it. A Hash scope that is optional, or that
sits under an optional one, went through the iterator instead, although
it has nothing to add there either: #validate only goes on once the
scope's params are there and not blank, so the iterator hands that one
Hash back, has no empty value to pass over and no dependency to meet.

The direct path now covers every scope with no Array scope and no
`given` on its chain. It no longer tries a required Hash scope inside an
Array scope, whose params resolve to an Array and were resolved a second
time by the iterator.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the perf/direct-optional-hash-scopes branch from 8aa2778 to 9ebb2d5 Compare November 11, 2026 10:16
@ericproulx
ericproulx merged commit 00c2a4e into master Nov 11, 2026
50 checks passed
@ericproulx
ericproulx deleted the perf/direct-optional-hash-scopes branch November 11, 2026 10:26
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