From 0caf98716165f8231e23834be1db4a782fd60810 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sat, 22 Aug 2026 17:32:56 +0800 Subject: [PATCH] perf: intern identifier field names for faster field lookup Motivation: Object field lookup is the hottest path in sjsonnet evaluation. Every field access (containsKey, containsVisibleKey, valueRaw) compares String keys char-by-char via .equals, which is wasteful when the same field names repeat across objects (common in K8s manifests, stdlib). Modification: - Parser routes identifier field names (fieldname rule and Expr.Select) through internedStrings, sharing String instances across repeated parses - String-literal field names are interned in the fieldname rule as well, with the same >1024 length guard as constructString to avoid memory bloat from pathologically large field names - With interned keys, String.equals hits its built-in reference check on the first line, so lookups short-circuit without char-by-char comparison Result: ParserBenchmark.main: 1.462 -> 1.378 ms/op (-5.7%); MainBenchmark.main within noise. All tests pass. Zero behavioral change -- interning only affects String identity, never equality semantics. --- sjsonnet/src/sjsonnet/Parser.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sjsonnet/src/sjsonnet/Parser.scala b/sjsonnet/src/sjsonnet/Parser.scala index 8af98afc..bdb339dc 100644 --- a/sjsonnet/src/sjsonnet/Parser.scala +++ b/sjsonnet/src/sjsonnet/Parser.scala @@ -679,7 +679,8 @@ class Parser( CharIn(".[({")./.!.flatMapX { s => val i = new Position(fileScope, implicitly[P[$]].index - 1) (s.charAt(0): @switch) match { - case '.' => Pass ~ id.map(x => Expr.Select(i, _: Expr, x)) + case '.' => + Pass ~ id.map(x => Expr.Select(i, _: Expr, internedStrings.getOrElseUpdate(x, x))) case '[' => Pass ~ (expr(currentDepth + 1).? ~ (":" ~ expr(currentDepth + 1).?).rep ~ "]").map { case (Some(tree), Seq()) => Expr.Lookup(i, _: Expr, tree) @@ -1008,8 +1009,12 @@ class Parser( def fieldname[$: P](currentDepth: Int): P[Expr.FieldName] = { P( - id.map(Expr.FieldName.Fixed.apply) | - string.map(Expr.FieldName.Fixed.apply) | + id.map(s => Expr.FieldName.Fixed(internedStrings.getOrElseUpdate(s, s))) | + string.map(s => + Expr.FieldName.Fixed( + if (s.length > 1024) s else internedStrings.getOrElseUpdate(s, s) + ) + ) | "[" ~ expr(currentDepth + 1).map(Expr.FieldName.Dyn.apply) ~ "]" ) }