perf: Eager evaluation for function args and object fields (batch 5)#704
perf: Eager evaluation for function args and object fields (batch 5)#704He-Pin wants to merge 1 commit intodatabricks:masterfrom
Conversation
Optimize function argument evaluation and object field construction by detecting expressions that can be evaluated immediately instead of creating lazy thunks (LazyExpr closures). Key optimizations: - tryEagerEval: For BinaryOp/UnaryOp with already-resolved operands, evaluate immediately and pass the result directly, avoiding thunk allocation and deferred evaluation overhead. - resolveAsDouble + tryInlineArith: Inline arithmetic fast path that resolves scope bindings directly to doubles and performs +/-/* without boxing through Val dispatch. - ConstMember fast path in visitMemberList: For object fields whose bodies don't reference self/super and whose parent-scope bindings are already resolved, pre-compute the value at object construction time. Three tiers: (1) Val literal -> direct, (2) ValidId with already-resolved binding -> direct, (3) canEagerEval + allRefsResolved -> try/catch eval with NonFatal fallback to lazy path. - Val.bool(b): Position-less singleton booleans for comparison results. - Val.Num.rawDouble: Direct double accessor for hot paths. Safety measures (addressed during cross-model review): - allRefsResolved runtime check ensures ValidId references are already Val instances, never forcing lazy thunks (preserves lazy semantics). - Select excluded from canEagerEval to prevent premature assertion triggering on parent-scope objects. - NonFatal catch (not Throwable) for eager eval fallback, consistent with project error handling policy. - Regression test for lazy semantics: unused field with error must not crash during object construction. Upstream references: - 2347db53 (tryEagerEval for BinaryOp/UnaryOp) - 9dc20016 (resolveAsDouble + tryInlineArith) - d4fc79d3 (canEagerEval for self-free objects)
Code ReviewI found several correctness and logic issues in this PR: 1.
|
Motivation
Jsonnet uses lazy evaluation by default — every expression is wrapped in a thunk (
Lazy) that defers computation until first access. However, many expressions are trivially evaluable (local variables, literal values, simple field access) and the overhead of creating, caching, and resolving thunks is wasted for these cases.Key Design Decision
Identify expressions that can be safely evaluated eagerly (without changing semantics) and evaluate them immediately during scope construction, bypassing the
Lazywrapper entirely. This eliminates:Safe candidates for eager evaluation:
ValidId)Modification
Evaluator.scala: Added eager evaluation paths for function arguments and object field initializationVal.scala: Support for detecting eagerly-evaluable expressionsBenchmark Results
JMH (JVM, 3 iterations)
Analysis
References
he-pin/sjsonnetjit branch commitsd4fc79d3,59cfddef,9d493100Result
Major performance improvement via selective eager evaluation: -30% comparison2, -10% realistic2/reverse by eliminating unnecessary thunk allocation for trivially-evaluable expressions.