Skip to content

Build the yield argument handler once, not per evaluation#2593

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf-yield-argument-handler
Jul 6, 2026
Merged

Build the yield argument handler once, not per evaluation#2593
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf-yield-argument-handler

Conversation

@lahma

@lahma lahma commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

\JintYieldExpression\ called \JintExpression.Build(expression.Argument)\ inside \EvaluateInternal\ — on every yield evaluation. \Build\ only returns a cached handler when the AST node's \UserData\ was pre-populated (prepared scripts), so for ordinary execution every \yield \ reconstructed the argument's whole handler subtree per iteration: for \yield { a: i, b: i + 1 }\ that is a \JintObjectExpression\ + \ObjectProperty[]\ + \ExpressionCache\ + identifier/binary handlers — every time the generator produced a value — and the handler lost all its per-node inline caches on each rebuild. The allocation census flagged this as ~51% of a generator-producing-literals workload (handler types should never appear in steady-state allocation profiles at all).

The argument handler is now built once in the constructor, like every other expression handler.

Measurements

Generator yielding object literals (100k yields, fresh engine per iteration, A/B/A/B alternating binaries): 132.2 → 52.9 MB/iter (−60%), 98.4 → 67.9 ms/iter (−31%). All AST-handler types disappear from the allocation profile; what remains is the literal's dictionary-path descriptors and the user-visible generator result objects.

Verification

  • \Jint.Tests\ green (net10.0 + net472), \Jint.Tests.PublicInterface\ green
  • Full test262: 99,260 passed, 0 failed (generator/async-generator suites cover yield, yield*, resume-with-throw/return and yields in loops)

🤖 Generated with Claude Code

JintYieldExpression called JintExpression.Build(expression.Argument) inside
EvaluateInternal — on every single yield evaluation. Build only returns a
cached handler when the AST node''s UserData was pre-populated (prepared
scripts), so for ordinary execution every `yield <expr>` reconstructed the
argument''s whole handler subtree per iteration: for `yield { a: i, b: i+1 }`
that is a JintObjectExpression + ObjectProperty[] + ExpressionCache +
JintIdentifierExpression and JintBinaryExpression handlers — every time the
generator produced a value. The handler also lost all its per-node inline
caches each rebuild.

The argument handler is now built once in the constructor like every other
expression handler.

Generator-producing-object-literals workload (fresh engine per iteration):
132.2 -> 52.9 MB/iter (-60%), 98.4 -> 67.9 ms/iter (-31%).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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