From 3ae5a56520ab200452e51ff5c7b09a76187d86bd Mon Sep 17 00:00:00 2001 From: He-Pin Date: Wed, 11 Mar 2026 20:28:17 +0800 Subject: [PATCH] perf: cached renderDouble for small integers + Builtin1/3 apply overrides Three optimizations: 1. RenderUtils.renderDouble: pre-cached string representations for integers 0-255, avoiding Long.toString allocation on every call. 2. Materializer.stringify: unified with renderDouble (removes duplicate integer fast-path code). 3. Builtin1.apply1 / Builtin3.apply3: added direct override methods that call evalRhs without creating intermediate Array. Upstream: jit branch commit 9917a682 --- sjsonnet/src/sjsonnet/Materializer.scala | 5 +---- sjsonnet/src/sjsonnet/Renderer.scala | 10 ++++++++-- sjsonnet/src/sjsonnet/Val.scala | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/sjsonnet/src/sjsonnet/Materializer.scala b/sjsonnet/src/sjsonnet/Materializer.scala index 42a891bc..1fe40b6e 100644 --- a/sjsonnet/src/sjsonnet/Materializer.scala +++ b/sjsonnet/src/sjsonnet/Materializer.scala @@ -33,10 +33,7 @@ abstract class Materializer { case Val.False(_) => "false" case Val.Null(_) => "null" case Val.Num(_, _) => - val d = v.asDouble - val l = d.toLong - if (l.toDouble == d) java.lang.Long.toString(l) - else RenderUtils.renderDouble(d) + RenderUtils.renderDouble(v.asDouble) case _ => apply0(v, new sjsonnet.Renderer()).toString } } diff --git a/sjsonnet/src/sjsonnet/Renderer.scala b/sjsonnet/src/sjsonnet/Renderer.scala index 5f925330..54069186 100644 --- a/sjsonnet/src/sjsonnet/Renderer.scala +++ b/sjsonnet/src/sjsonnet/Renderer.scala @@ -268,12 +268,18 @@ final case class MaterializeJsonRenderer( object RenderUtils { + // Pre-cached string representations of small integers (0-255) + private val intStrCache: Array[String] = Array.tabulate(256)(_.toString) + /** * Custom rendering of Doubles used in rendering */ def renderDouble(d: Double): String = { - if (d.toLong == d) d.toLong.toString - else if (d % 1 == 0) { + val l = d.toLong + if (l.toDouble == d) { + if (l >= 0 && l < 256) intStrCache(l.toInt) + else l.toString + } else if (d % 1 == 0) { BigDecimal(d).setScale(0, BigDecimal.RoundingMode.HALF_EVEN).toBigInt.toString() } else d.toString } diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index c717c93c..9377aabc 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -1869,6 +1869,15 @@ object Val { tailstrictMode: TailstrictMode): Val = if (namedNames == null && argVals.length == 1) evalRhs(argVals(0).value, ev, outerPos) else super.apply(argVals, namedNames, outerPos) + + override def apply1(argVal: Eval, outerPos: Position)(implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = + if (params.names.length != 1) apply(Array(argVal), null, outerPos) + else { + if (tailstrictMode == TailstrictModeEnabled) argVal.value + evalRhs(argVal.value, ev, outerPos) + } } abstract class Builtin2(fn: String, pn1: String, pn2: String, defs: Array[Expr] = null) @@ -1912,6 +1921,19 @@ object Val { if (namedNames == null && argVals.length == 3) evalRhs(argVals(0).value, argVals(1).value, argVals(2).value, ev, outerPos) else super.apply(argVals, namedNames, outerPos) + + override def apply3(argVal1: Eval, argVal2: Eval, argVal3: Eval, outerPos: Position)(implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = + if (params.names.length != 3) apply(Array(argVal1, argVal2, argVal3), null, outerPos) + else { + if (tailstrictMode == TailstrictModeEnabled) { + argVal1.value + argVal2.value + argVal3.value + } + evalRhs(argVal1.value, argVal2.value, argVal3.value, ev, outerPos) + } } abstract class Builtin4(