Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions sjsonnet/src/sjsonnet/Importer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
1 change: 1 addition & 0 deletions sjsonnet/src/sjsonnet/ValVisitor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
8 changes: 5 additions & 3 deletions sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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
Expand Down
35 changes: 21 additions & 14 deletions sjsonnet/src/sjsonnet/stdlib/SetModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this change is introducing an O(n^2) bug because now we're making a fresh copy of the array on every loop iteration.

I've opened #575 with a suggested fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need just use the length to fix this.

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) = {
Expand Down Expand Up @@ -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) =>
Expand All @@ -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) =>
Expand Down
10 changes: 6 additions & 4 deletions sjsonnet/src/sjsonnet/stdlib/StringModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down