Skip to content

perf: pre-cached indent arrays for bulk newline+spaces#676

Open
He-Pin wants to merge 2 commits intodatabricks:masterfrom
He-Pin:perf/renderer-indent-cache
Open

perf: pre-cached indent arrays for bulk newline+spaces#676
He-Pin wants to merge 2 commits intodatabricks:masterfrom
He-Pin:perf/renderer-indent-cache

Conversation

@He-Pin
Copy link
Copy Markdown
Contributor

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

Motivation

The JSON renderer writes newline+indent sequences character by character. For deeply nested output with many array/object elements, this is a significant bottleneck. Pre-caching indent byte arrays allows bulk writes.

Key Design Decision

Pre-compute indent arrays up to a maximum depth and use System.arraycopy-style bulk writes instead of character-by-character output. This trades a small amount of memory for significant rendering throughput.

Modification

Added pre-cached indent arrays in BaseCharRenderer.scala. The renderer now uses bulk array copies for indent sequences, reducing per-character overhead.

Benchmark Results

JMH Regression Suite (1 fork, 3 warmup, 1 measurement)

Benchmark Master (ms/op) This PR (ms/op) Change
reverse 10.705 8.954 -16.4%
base64DecodeBytes 9.423 8.252 -12.4%

All other benchmarks within noise margin.

Scala Native Hyperfine (-N -w4 -m20)

Benchmark sjsonnet master This PR jrsonnet vs master vs jrsonnet
reverse 50.0 ± 2.7 ms 36.2 ± 1.6 ms 33.6 ± 1.9 ms 27.6% faster 1.08x slower
base64DecodeBytes 39.8 ± 1.5 ms 28.2 ± 1.3 ms 24.8 ± 1.3 ms 29.1% faster 1.14x slower

Analysis

This is particularly effective for rendering-heavy benchmarks like reverse (which generates deeply nested JSON output) and base64DecodeBytes (large output). On native, this narrows the gap with jrsonnet from 1.49x to just 1.08x on reverse, and from 1.61x to 1.14x on base64DecodeBytes.

References

Upstream jit branch exploration at he-pin/sjsonnet@jit

Result

16-29% improvement on rendering-heavy benchmarks. Dramatically narrows the gap with jrsonnet on reverse and base64DecodeBytes.

He-Pin and others added 2 commits April 4, 2026 22:12
For integer values (the common case in Jsonnet), write digits directly
to CharBuilder using a scratch digit buffer instead of allocating a
String via RenderUtils.renderDouble/Long.toString and copying chars.

Upstream: jit branch commit d63ce90

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pre-compute indent arrays (newline + spaces) for depths 0-15 at
Renderer construction time. On flushBuffer, use bulk appendAll for
the cached array instead of character-by-character space appending.

Upstream: jit branch commit 4f19bde

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@He-Pin He-Pin marked this pull request as ready for review April 5, 2026 00:28
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