Skip to content

Commit d732290

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 4123ac3 commit d732290

3 files changed

Lines changed: 15 additions & 4 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: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,17 @@ object Val {
886886
*/
887887
@volatile private[sjsonnet] var _sortedInlineOrder: Array[Int] = null
888888

889+
@volatile 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+
}
899+
889900
/**
890901
* When true, field caching can be skipped during materialization because no field body
891902
* references `self` or `super`. This eliminates HashMap allocation overhead for objects with >2
@@ -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)