From ffb3174e8b41902ae4a5781a61e8249c66409fc7 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Tue, 9 Dec 2025 19:49:47 +0800 Subject: [PATCH] chore: Use ArrayBuilder to build the array --- sjsonnet/src/sjsonnet/Evaluator.scala | 10 ++++++ sjsonnet/src/sjsonnet/Importer.scala | 8 +++-- sjsonnet/src/sjsonnet/ValVisitor.scala | 1 + .../src/sjsonnet/stdlib/ArrayModule.scala | 8 +++-- sjsonnet/src/sjsonnet/stdlib/SetModule.scala | 35 +++++++++++-------- .../src/sjsonnet/stdlib/StringModule.scala | 10 +++--- 6 files changed, 48 insertions(+), 24 deletions(-) diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index d42598fe..b3521a15 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -811,6 +811,16 @@ class Evaluator( final def visitComp(f: List[CompSpec], scopes: Array[ValScope]): Array[ValScope] = f match { case (spec @ ForSpec(_, name, expr)) :: rest => val newScopes = collection.mutable.ArrayBuilder.make[ValScope] + // Set a reasonable size hint based on heuristic estimation to avoid full traversal + if (scopes.length > 0) { + val firstArrSize = visitExpr(expr)(scopes(0)) match { + case a: Val.Arr => a.length + case _ => 1 + } + // Use heuristic estimation: first array size * scopes count * 2 for safety margin + newScopes.sizeHint(math.max(16, firstArrSize * scopes.length * 2)) + } + var i = 0 while (i < scopes.length) { val s = scopes(i) diff --git a/sjsonnet/src/sjsonnet/Importer.scala b/sjsonnet/src/sjsonnet/Importer.scala index 882cdb75..35600cac 100644 --- a/sjsonnet/src/sjsonnet/Importer.scala +++ b/sjsonnet/src/sjsonnet/Importer.scala @@ -57,21 +57,23 @@ final case class FileParserInput(file: File) extends ParserInput { override def checkTraceable(): Unit = {} private lazy val lineNumberLookup: Array[Int] = { - val lines = mutable.ArrayBuffer[Int](0) + val lines = new mutable.ArrayBuilder.ofInt + lines.sizeHint(100) // reasonable initial size hint + lines.+=(0) val bufferedStream = new BufferedInputStream(new FileInputStream(file)) var byteRead: Int = 0 var currentPosition = 0 while ({ byteRead = bufferedStream.read(); byteRead != -1 }) { if (byteRead == '\n') { - lines += currentPosition + 1 + lines.+=(currentPosition + 1) } currentPosition += 1 } bufferedStream.close() - lines.toArray + lines.result() } def prettyIndex(index: Int): String = diff --git a/sjsonnet/src/sjsonnet/ValVisitor.scala b/sjsonnet/src/sjsonnet/ValVisitor.scala index e6b11ee6..f19b37f7 100644 --- a/sjsonnet/src/sjsonnet/ValVisitor.scala +++ b/sjsonnet/src/sjsonnet/ValVisitor.scala @@ -15,6 +15,7 @@ class ValVisitor(pos: Position) extends JsVisitor[Val, Val] { self => def visitArray(length: Int, index: Int): ArrVisitor[Val, Val] = new ArrVisitor[Val, Val] { val a = new mutable.ArrayBuilder.ofRef[Lazy] + if (length >= 0) a.sizeHint(length) def subVisitor: Visitor[?, ?] = self def visitValue(v: Val, index: Int): Unit = a.+=(v) def visitEnd(index: Int): Val = Val.Arr(pos, a.result()) diff --git a/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala b/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala index ab4a5e79..00b2af74 100644 --- a/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala @@ -197,6 +197,7 @@ object ArrayModule extends AbstractFunctionModule { def evalRhs(value: Lazy, _arr: Lazy, ev: EvalScope, pos: Position): Val = { val arr = _arr.force.asArr val b = new mutable.ArrayBuilder.ofRef[Lazy] + b.sizeHint(arr.length) // Size hint based on array length (worst case) var i = 0 while (i < arr.length) { if (ev.equal(arr.force(i), value.force)) { @@ -428,13 +429,14 @@ object ArrayModule extends AbstractFunctionModule { Val.Str(pos, builder.toString()) case a: Val.Arr => val lazyArray = a.asLazyArray - val out = new mutable.ArrayBuffer[Lazy](lazyArray.length * count) + val out = new mutable.ArrayBuilder.ofRef[Lazy] + out.sizeHint(lazyArray.length * count) var i = 0 while (i < count) { - out.appendAll(lazyArray) + out ++= lazyArray i += 1 } - Val.Arr(pos, out.toArray) + Val.Arr(pos, out.result()) case x => Error.fail("std.repeat first argument must be an array or a string") } res diff --git a/sjsonnet/src/sjsonnet/stdlib/SetModule.scala b/sjsonnet/src/sjsonnet/stdlib/SetModule.scala index 4e1760bd..65f4a364 100644 --- a/sjsonnet/src/sjsonnet/stdlib/SetModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/SetModule.scala @@ -62,25 +62,28 @@ object SetModule extends AbstractFunctionModule { return arr } - val out = new mutable.ArrayBuffer[Lazy] + val out = new mutable.ArrayBuilder.ofRef[Lazy] + // Set a reasonable size hint - in the worst case (no duplicates), we'll need arrValue.length elements + out.sizeHint(arrValue.length) for (v <- arrValue) { - if (out.isEmpty) { - out.append(v) + val outResult = out.result() + if (outResult.length == 0) { + out.+=(v) } else if (keyF.isInstanceOf[Val.False]) { - if (!ev.equal(out.last.force, v.force)) { - out.append(v) + if (!ev.equal(outResult.last.force, v.force)) { + out.+=(v) } } else if (!keyF.isInstanceOf[Val.False]) { val keyFFunc = keyF.asInstanceOf[Val.Func] val o1Key = keyFFunc.apply1(v, pos.noOffset)(ev, TailstrictModeDisabled) - val o2Key = keyFFunc.apply1(out.last, pos.noOffset)(ev, TailstrictModeDisabled) + val o2Key = keyFFunc.apply1(outResult.last, pos.noOffset)(ev, TailstrictModeDisabled) if (!ev.equal(o1Key, o2Key)) { - out.append(v) + out.+=(v) } } } - Val.Arr(pos, out.toArray) + Val.Arr(pos, out.result()) } private def sortArr(pos: Position, ev: EvalScope, arr: Val, keyF: Val) = { @@ -188,16 +191,18 @@ object SetModule extends AbstractFunctionModule { val a = toArrOrString(args(0), pos, ev) val b = toArrOrString(args(1), pos, ev) - val out = new mutable.ArrayBuffer[Lazy] + val out = new mutable.ArrayBuilder.ofRef[Lazy] + // Set a reasonable size hint - intersection will be at most the size of the smaller set + out.sizeHint(math.min(a.length, b.length)) // The intersection will always be, at most, the size of the smallest set. val sets = if (b.length < a.length) (b, a) else (a, b) for (v <- sets._1) { if (existsInSet(ev, pos, keyF, sets._2, v.force)) { - out.append(v) + out.+=(v) } } - Val.Arr(pos, out.toArray) + Val.Arr(pos, out.result()) }, builtinWithDefaults("setDiff", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) => @@ -207,14 +212,16 @@ object SetModule extends AbstractFunctionModule { val a = toArrOrString(args(0), pos, ev) val b = toArrOrString(args(1), pos, ev) - val out = new mutable.ArrayBuffer[Lazy] + val out = new mutable.ArrayBuilder.ofRef[Lazy] + // Set a reasonable size hint - difference will be at most the size of the first set + out.sizeHint(a.length) for (v <- a) { if (!existsInSet(ev, pos, keyF, b, v.force)) { - out.append(v) + out.+=(v) } } - Val.Arr(pos, out.toArray) + Val.Arr(pos, out.result()) }, builtinWithDefaults("setMember", "x" -> null, "arr" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) => diff --git a/sjsonnet/src/sjsonnet/stdlib/StringModule.scala b/sjsonnet/src/sjsonnet/stdlib/StringModule.scala index 62c6d8cf..7f44b316 100644 --- a/sjsonnet/src/sjsonnet/stdlib/StringModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/StringModule.scala @@ -205,19 +205,21 @@ object StringModule extends AbstractFunctionModule { } Val.Str(pos, b.toString) case sep: Val.Arr => - val out = new mutable.ArrayBuffer[Lazy] + val out = new mutable.ArrayBuilder.ofRef[Lazy] + // Set a reasonable size hint based on estimated result size + out.sizeHint(arr.length * 2) var added = false for (x <- arr) { x match { case Val.Null(_) => // do nothing case v: Val.Arr => - if (added) out.appendAll(sep.asLazyArray) + if (added) out ++= sep.asLazyArray added = true - out.appendAll(v.asLazyArray) + out ++= v.asLazyArray case x => Error.fail("Cannot join " + x.prettyName) } } - Val.Arr(pos, out.toArray) + Val.Arr(pos, out.result()) case x => Error.fail("Cannot join " + x.prettyName) } }