From c231f70df55eefef6d7a0f20c8e04b65f8760acb Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sat, 30 May 2026 15:42:04 +0800 Subject: [PATCH] perf: capture parse Position without boxing the offset Int MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: Parser.Pos is invoked for nearly every AST node. It was `Index.map(off => new Position(...))`: fastparse's `Index` stores the offset as an Int in its `successValue: Any` field (boxing it), and the `.map` then unboxes it and allocates a closure — per node. boxToInteger via SharedPackageDefs.Index was a top self-frame in the parse flamegraph on kube-prometheus. Modification: - Rewrite Pos to write the Position object straight into successValue via ctx.freshSuccess(new Position(fileScope, ctx.index)), skipping the Int box/unbox and the map closure. Parse output (positions/errors) is unchanged. Result: JMH ParserBenchmark (parse-only, all test-suite files): 1.669 -> 1.579 ms/op (+5.4%, non-overlapping bands). Native parse_time on kube-prometheus: ~105.6 -> ~100.9 ms (+4.5%, consistent). Output byte-identical. 450/450 tests pass. --- sjsonnet/src/sjsonnet/Parser.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sjsonnet/src/sjsonnet/Parser.scala b/sjsonnet/src/sjsonnet/Parser.scala index 30a2f0e6..f667689b 100644 --- a/sjsonnet/src/sjsonnet/Parser.scala +++ b/sjsonnet/src/sjsonnet/Parser.scala @@ -96,7 +96,16 @@ class Parser( throw new ParseError(msg, offset = offset) } - def Pos[$: P]: P[Position] = Index.map(offset => new Position(fileScope, offset)) + // Capture the current parse offset as a Position directly, rather than `Index.map(...)`. + // `Index` stores the offset as an `Int` in fastparse's `successValue: Any`, boxing it, and the + // `.map` then unboxes it and allocates a closure — both per AST node (Pos is called for nearly + // every node). Writing the Position straight into successValue (a reference) skips the box/unbox + // and the lambda. boxToInteger via SharedPackageDefs.Index was a top self-frame in the parse + // flamegraph on kube-prometheus. + def Pos[$: P]: P[Position] = { + val ctx = implicitly[P[$]] + ctx.freshSuccess(new Position(fileScope, ctx.index)) + } def id[$: P]: P[String] = P( CharIn("_a-zA-Z") ~~