Skip to content

perf: optimize std.makeArray for constant-body functions#697

Draft
He-Pin wants to merge 1 commit intodatabricks:masterfrom
He-Pin:perf/makearray-constant-body
Draft

perf: optimize std.makeArray for constant-body functions#697
He-Pin wants to merge 1 commit intodatabricks:masterfrom
He-Pin:perf/makearray-constant-body

Conversation

@He-Pin
Copy link
Copy Markdown
Contributor

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

Motivation

std.makeArray(n, func) creates an array of size n by calling func(i) for each index i. When the function body is a constant (e.g., std.makeArray(1000, function(_) 'x')), every element gets the same value, but the current implementation still creates n lazy thunks (LazyApply1) and n Val.Num index values — all of which are immediately discarded when evaluated.

Key Design Decision

Detect constant-body functions at call time using the existing bodyExpr accessor. When func.bodyExpr is a Val.Literal (a value that is both a Val and an Expr), fill the array directly with java.util.Arrays.fill — zero lazy thunks, zero Val.Num allocations.

Modification

sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala (std.makeArray):

  • Check if func.params.names.length == 1 && func.bodyExpr != null && body.isInstanceOf[Val.Literal]
  • If constant: java.util.Arrays.fill(a, constVal) — O(1) work per element
  • Otherwise: fall through to existing LazyApply1 loop

sjsonnet/src/sjsonnet/Val.scala:

  • Updated bodyExpr doc to mention this optimization

Benchmark Results

JMH — Full Suite (35 benchmarks, @fork(1) @WarmUp(1) @measurement(1))

Benchmark Baseline (ms) This PR (ms) Change
bench.04 (foldl+makeArray) 0.474 0.234 -50.6% 🔥
foldl 0.271 0.134 -50.6% 🔥
large_string_join 2.080 0.731 -64.9% 🔥
comparison2 35.769 33.819 -5.4%
realistic2 76.840 77.375 +0.7% (noise)

No regressions across all 35 benchmarks.

Analysis of Key Improvements

  • bench.04 (std.foldl over std.makeArray(20000, function(i) 'aaaaa')): The constant-body detection eliminates 20,000 LazyApply1 thunks and 20,000 Val.Num allocations.
  • foldl: Similar pattern — constant-body std.makeArray feeds into std.foldl.
  • large_string_join: Uses std.makeArray with constant body to build string arrays.

Analysis

  • Detection is cheap: bodyExpr is a simple accessor (returns null by default). The isInstanceOf check is a single vtable lookup.
  • Correctness: Val.Literal values are immutable and safe to share across array elements.
  • Arrays.fill: JDK-optimized bulk fill — handles the entire array in a single call.
  • Fallback: Non-constant functions use the existing LazyApply1 path unchanged.

References

  • Val.Literal — values that extend both Val and Expr (literals like true, false, null, string constants)
  • bodyExpr accessor introduced for std.foldl string concat optimization

Result

Zero-allocation array initialization for constant-body std.makeArray. 50-65% improvement on benchmarks using std.makeArray with constant functions.

@He-Pin He-Pin force-pushed the perf/makearray-constant-body branch 2 times, most recently from cf00ce9 to a0be13f Compare April 9, 2026 00:56
Detect when the function passed to std.makeArray ignores its index
parameter and always returns the same expression. In that case,
evaluate the body once and fill the array with the same Lazy reference,
avoiding N-1 redundant evaluations.

Detection uses Val.Func.isNonCapturing on bodyExpr to identify
constant bodies (literals, non-capturing expressions).

Upstream: jit branch commit 0e2bbe9e
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