Skip to content

perf: extend comprehension scope reuse to builtins, select, lookup#783

Merged
stephenamar-db merged 1 commit intodatabricks:masterfrom
He-Pin:perf/extend-scope-reuse
Apr 21, 2026
Merged

perf: extend comprehension scope reuse to builtins, select, lookup#783
stephenamar-db merged 1 commit intodatabricks:masterfrom
He-Pin:perf/extend-scope-reuse

Conversation

@He-Pin
Copy link
Copy Markdown
Contributor

@He-Pin He-Pin commented Apr 18, 2026

Summary

  • Extend isNonCapturingBody to recognize more expression types that produce eager values without capturing the comprehension ValScope
  • This allows the mutable scope reuse fast path to activate for common comprehension patterns like [std.length(x) for x in arr] or [x.field for x in arr], avoiding per-iteration ValScope allocation via scope.extendSimple() + Arrays.copyOf

New expression types covered

Expression Check
ApplyBuiltin0 always true (no args)
ApplyBuiltin1 isNonCapturingBody(a1)
ApplyBuiltin2 both args non-capturing
ApplyBuiltin3 all 3 args non-capturing
ApplyBuiltin4 all 4 args non-capturing
Select isNonCapturingBody(value)
Lookup both value and index non-capturing

Safety argument

The mutable scope reuse path evaluates the body with visitExpr (eager). The returned Val doesn't capture the comprehension scope. Internal visitAsLazy calls within builtins receive non-capturing sub-expressions, so visitAsLazy returns eagerly via tryEagerEval or direct binding lookup — no LazyExpr creation, no stale scope capture.

NOT extended to: Apply0-3 (user-defined function calls may create closures that capture scope), Function (creates Val.Func capturing scope), ObjBody.MemberList (creates Val.Obj with lazy fields capturing scope).

Test plan

  • ./mill 'sjsonnet.jvm[3.3.7]'.test — all tests pass
  • ./mill 'sjsonnet.jvm[3.3.7]'.compile — compiles clean
  • isInvariantExpr (line 478+) already covers the same expression types, confirming this pattern is established

Motivation:
The mutable scope reuse path in comprehension evaluation avoids O(n)
ValScope allocations (Arrays.copyOf per iteration), but was gated by
isNonCapturingBody which only covered ValidId, BinaryOp, UnaryOp,
And, Or, IfElse, and Literal. Common patterns like [std.length(x) for
x in arr] or [x.field for x in arr] fell through to the per-iteration
scope allocation path.

Modification:
Extend isNonCapturingBody to cover ApplyBuiltin0-4, Select, and Lookup
with recursive argument checks. These produce eager values via visitExpr
and don't create closures that capture the comprehension scope. The same
expression types are already handled by isInvariantExpr.

Result:
More comprehension patterns benefit from mutable scope reuse, eliminating
per-iteration ValScope allocation + Arrays.copyOf overhead.
@stephenamar-db stephenamar-db merged commit c547cf8 into databricks:master Apr 21, 2026
5 checks passed
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.

2 participants