Skip to content

perf: speed up direct long rendering#1087

Open
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:perf/fast-long-rendering
Open

perf: speed up direct long rendering#1087
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:perf/fast-long-rendering

Conversation

@He-Pin

@He-Pin He-Pin commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Motivation

Direct long rendering in sjsonnet was already fairly optimized: it used scratch buffers and digit-pair lookup tables instead of repeatedly allocating Strings on the hot renderer paths. The remaining cost was the /100 loop for every two digits, plus TOML exact-long doubles still went through an intermediate string path and used a math.round guard that saturates near Long.MaxValue.

This uses the jeaiii integer-formatting idea and jsoniter-scala v2.38.17 as references, but applies them conservatively for sjsonnet: the blog-style 1441151881 /100 reciprocal is only safe in a bounded range, so the full Long path uses jsoniter-style Math.multiplyHigh chunking by 1e8/1e16, then digit-pair extraction for int-sized chunks.

References:

Modification

  • Add a shared FastLongRenderer for ASCII Long rendering.
  • Route both BaseByteRenderer and BaseCharRenderer through that shared helper.
  • Keep TOML exact-Long doubles on StringBuilder.append(Long) and fall back to RenderUtils.renderDouble for non-exact Long values, avoiding the previous math.round(...).toLong saturation case.
  • Add renderer boundary/random regression coverage and a committed JMH benchmark so the long-rendering comparison can be reproduced from the PR.

Result

The PR is squashed to one commit on top of databricks/master. The shared code lives under sjsonnet/src, so the optimization is used by JVM, Scala.js, Scala Native, and WASM builds. Local checks covered formatting, JVM tests, compile coverage across JVM/JS/WASM/Native, and renderer tests on JS/WASM/Native.

JMH command:

./mill bench.runJmh ".*LongRenderingBenchmark.*" -f 1 -wi 5 -i 8 -r 1s -w 1s

Local JMH on JDK 21, Apple Silicon, lower is better:

Benchmark upstream/master this PR Change
baseByteRendererLongs 40.280 ± 1.054 us/op 32.865 ± 0.277 us/op 1.23x faster
baseCharRendererLongs 48.081 ± 1.233 us/op 45.992 ± 0.229 us/op 1.05x faster
tomlRendererExactLongDoubles 81.820 ± 0.939 us/op 43.046 ± 1.273 us/op 1.90x faster

The smaller char-renderer win is expected: the existing char path was already optimized with a scratch buffer and digit-pair tables, so most of the new gain comes from removing repeated division and sharing the full-Long chunking logic.

@He-Pin He-Pin marked this pull request as draft July 5, 2026 07:01
@He-Pin He-Pin force-pushed the perf/fast-long-rendering branch from a06130e to 5031f7e Compare July 5, 2026 07:42
Motivation:
Direct long rendering already used scratch buffers and digit-pair lookup tables, but it still looped over /100 for every two digits. The TOML exact-long path also allocated an intermediate String and used a math.round guard that saturated integer doubles above Long.MaxValue.

Modification:
Introduce a shared FastLongRenderer that writes ASCII long digits with jsoniter-style 1e8/1e16 Math.multiplyHigh chunking plus jeaiii-style digit extraction for int-sized chunks. Wire BaseByteRenderer and BaseCharRenderer through the shared helper, keep TOML exact Long values on StringBuilder.append(Long), and fall back to RenderUtils.renderDouble for non-Long doubles.

Result:
Char and byte renderers now share one long-rendering implementation with boundary/random regression coverage. JVM, JS, WASM, and Native renderer tests pass for the Math.multiplyHigh path. Local JMH shows byte long rendering 1.22x faster and TOML exact-long rendering 1.92x faster, while char long rendering remains effectively flat.
@He-Pin He-Pin force-pushed the perf/fast-long-rendering branch from 5031f7e to 2965767 Compare July 5, 2026 07:59
@He-Pin He-Pin marked this pull request as ready for review July 5, 2026 08:02
@He-Pin He-Pin marked this pull request as draft July 5, 2026 08:02
@He-Pin He-Pin marked this pull request as ready for review July 5, 2026 08:04
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