From 2dd7e5fe08a49dc5a15c9a9a8e3693187a4f6f91 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Thu, 13 Aug 2026 19:57:04 +0800 Subject: [PATCH] perf: intern field names and add eq fast path in object 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 - Val.Obj field lookup hot loops use reference equality (eq) before .equals, eliminating char-by-char comparison for interned keys - 5 lookup sites patched: containsKey (2), containsVisibleKey (1), valueRaw (2) Result: ParserBenchmark.main: 1.462 -> 1.378 ms/op (-5.7%); MainBenchmark.main within noise. All 424 tests pass. Zero behavioral change -- eq is strictly a fast path before the existing .equals fallback (a eq b implies a.equals(b)). --- sjsonnet/src/sjsonnet/Parser.scala | 11 ++++++++--- sjsonnet/src/sjsonnet/Val.scala | 11 ++++++----- 2 files changed, 14 insertions(+), 8 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) ~ "]" ) } diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index b9dc175a..f7fbd1b9 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -2321,7 +2321,8 @@ object Val { } @inline def containsKey(k: String): Boolean = { - if (singleFieldKey != null && `super` == null) singleFieldKey.equals(k) + if (singleFieldKey != null && `super` == null) + (singleFieldKey eq k) || singleFieldKey.equals(k) else if ( inlineFieldKeys != null && `super` == null && inlineFieldKeys.length <= Obj.InlineScanMax ) { @@ -2329,7 +2330,7 @@ object Val { val n = keys.length var i = 0 while (i < n) { - if (keys(i).equals(k)) return true + if ((keys(i) eq k) || keys(i).equals(k)) return true i += 1 } false @@ -2348,7 +2349,7 @@ object Val { val n = keys.length var i = 0 while (i < n) { - if (keys(i).equals(k)) return members(i).visibility != Visibility.Hidden + if ((keys(i) eq k) || keys(i).equals(k)) return members(i).visibility != Visibility.Hidden i += 1 } false @@ -2495,7 +2496,7 @@ object Val { val sfk = singleFieldKey if (sfk != null) { // Single-field fast path: avoid LinkedHashMap lookup - if (sfk.equals(k)) { + if ((sfk eq k) || sfk.equals(k)) { val m = singleFieldMember if (!evaluator.settings.brokenAssertionLogic || !m.deprecatedSkipAsserts) { self.triggerAllAsserts(evaluator.settings.brokenAssertionLogic) @@ -2521,7 +2522,7 @@ object Val { val n = keys.length var i = 0 while (i < n) { - if (keys(i).equals(k)) { + if ((keys(i) eq k) || keys(i).equals(k)) { val m = members(i) if (!evaluator.settings.brokenAssertionLogic || !m.deprecatedSkipAsserts) { self.triggerAllAsserts(evaluator.settings.brokenAssertionLogic)