Skip to content

Commit 5ccab84

Browse files
committed
perf: cache sorted visible key names on Val.Obj
Motivation: Non-inline objects (>8 fields, super chains, excludedKeys) call .sorted(CodepointStringOrdering) on visibleKeyNames at every materialization. This re-sorts and allocates a new array each time. Modification: Add _sortedVisibleKeyNames cache on Val.Obj with lazy initialization. Replace all 5 call sites (Materializer recursive/stackless, Renderer, ByteRenderer, Val.Obj.foreachElement) with the cached accessor. Result: Objects with stable schemas sort their key names once and reuse the result on subsequent materializations.
1 parent b63fb40 commit 5ccab84

3 files changed

Lines changed: 16 additions & 5 deletions

File tree

sjsonnet/src/sjsonnet/ByteRenderer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ class ByteRenderer(out: OutputStream = new java.io.ByteArrayOutputStream(), inde
391391
matDepth: Int,
392392
ctx: Materializer.MaterializeContext)(implicit evaluator: EvalScope): Unit = {
393393
val keys =
394-
if (ctx.sort) obj.visibleKeyNames.sorted(Util.CodepointStringOrdering)
394+
if (ctx.sort) obj.sortedVisibleKeyNames
395395
else obj.visibleKeyNames
396396

397397
openObjBrace()

sjsonnet/src/sjsonnet/Materializer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ abstract class Materializer {
100100
else materializeInlineObj(obj, visitor, depth, ctx)
101101
} else {
102102
val keys =
103-
if (ctx.sort) obj.visibleKeyNames.sorted(Util.CodepointStringOrdering)
103+
if (ctx.sort) obj.sortedVisibleKeyNames
104104
else obj.visibleKeyNames
105105
val ov = visitor.visitObject(keys.length, jsonableKeys = true, -1)
106106
var i = 0
@@ -432,7 +432,7 @@ abstract class Materializer {
432432
storePos(obj.pos)
433433
obj.triggerAllAsserts(ctx.brokenAssertionLogic)
434434
val keyNames =
435-
if (ctx.sort) obj.visibleKeyNames.sorted(Util.CodepointStringOrdering)
435+
if (ctx.sort) obj.sortedVisibleKeyNames
436436
else obj.visibleKeyNames
437437
val objVisitor = visitor.visitObject(keyNames.length, jsonableKeys = true, -1)
438438
stack.push(

sjsonnet/src/sjsonnet/Val.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,18 @@ object Val {
884884
* Cached sorted field order for inline objects. Shared across all objects from the same
885885
* MemberList to avoid per-object sort + allocation.
886886
*/
887-
@volatile private[sjsonnet] var _sortedInlineOrder: Array[Int] = null
887+
private[sjsonnet] var _sortedInlineOrder: Array[Int] = null
888+
889+
private[sjsonnet] var _sortedVisibleKeyNames: Array[String] = null
890+
891+
private[sjsonnet] def sortedVisibleKeyNames: Array[String] = {
892+
var r = _sortedVisibleKeyNames
893+
if (r == null) {
894+
r = visibleKeyNames.sorted(Util.CodepointStringOrdering)
895+
_sortedVisibleKeyNames = r
896+
}
897+
r
898+
}
888899

889900
/**
890901
* When true, field caching can be skipped during materialization because no field body
@@ -1402,7 +1413,7 @@ object Val {
14021413

14031414
def foreachElement(sort: Boolean, pos: Position)(f: (String, Val) => Unit)(implicit
14041415
ev: EvalScope): Unit = {
1405-
val keys = if (sort) visibleKeyNames.sorted(Util.CodepointStringOrdering) else visibleKeyNames
1416+
val keys = if (sort) sortedVisibleKeyNames else visibleKeyNames
14061417
for (k <- keys) {
14071418
val v = value(k, pos)
14081419
f(k, v)

0 commit comments

Comments
 (0)