diff --git a/sjsonnet/src/sjsonnet/Materializer.scala b/sjsonnet/src/sjsonnet/Materializer.scala index 9892e3da..f0767b8e 100644 --- a/sjsonnet/src/sjsonnet/Materializer.scala +++ b/sjsonnet/src/sjsonnet/Materializer.scala @@ -619,20 +619,96 @@ object Materializer extends Materializer { } i += 1 } - // Insertion sort by key name (optimal for 2-8 elements) - i = 1 - while (i < visCount) { + sortInlineOrder(order, keys, visCount) + order + } + + private def sortInlineOrder(order: Array[Int], keys: Array[String], len: Int): Unit = { + if (len <= 1) return + if (len <= 16) insertionSortInlineOrder(order, keys, 0, len - 1) + else quickSortInlineOrder(order, keys, 0, len - 1) + } + + private def insertionSortInlineOrder( + order: Array[Int], + keys: Array[String], + left: Int, + right: Int): Unit = { + var i = left + 1 + while (i <= right) { val pivotIdx = order(i) val pivotKey = keys(pivotIdx) var j = i - 1 - while (j >= 0 && Util.compareStringsByCodepoint(keys(order(j)), pivotKey) > 0) { + while (j >= left && Util.compareStringsByCodepoint(keys(order(j)), pivotKey) > 0) { order(j + 1) = order(j) j -= 1 } order(j + 1) = pivotIdx i += 1 } - order + } + + private def quickSortInlineOrder( + order: Array[Int], + keys: Array[String], + left0: Int, + right0: Int): Unit = { + var left = left0 + var right = right0 + while (right - left > 16) { + val pivotKey = medianOfThreeKey(order, keys, left, right) + var i = left + var j = right + while (i <= j) { + while (Util.compareStringsByCodepoint(keys(order(i)), pivotKey) < 0) i += 1 + while (Util.compareStringsByCodepoint(keys(order(j)), pivotKey) > 0) j -= 1 + if (i <= j) { + val tmp = order(i) + order(i) = order(j) + order(j) = tmp + i += 1 + j -= 1 + } + } + if (j - left < right - i) { + if (left < j) quickSortInlineOrder(order, keys, left, j) + left = i + } else { + if (i < right) quickSortInlineOrder(order, keys, i, right) + right = j + } + } + insertionSortInlineOrder(order, keys, left, right) + } + + /** + * Median-of-three pivot selection for [[quickSortInlineOrder]]. Returns the median key among + * `keys(order(left))`, `keys(order(mid))`, and `keys(order(right))` (where `mid = (left + right) + * >>> 1`). Compared to a fixed mid-element pivot, this reduces worst-case behaviour on inputs + * that are already sorted, reverse-sorted, or contain runs — patterns that occur frequently in + * Jsonnet object key sets. + */ + private def medianOfThreeKey( + order: Array[Int], + keys: Array[String], + left: Int, + right: Int): String = { + val mid = (left + right) >>> 1 + val a = keys(order(left)) + val b = keys(order(mid)) + val c = keys(order(right)) + val ab = Util.compareStringsByCodepoint(a, b) + val ac = Util.compareStringsByCodepoint(a, c) + val bc = Util.compareStringsByCodepoint(b, c) + if (ab <= 0) { + if (bc <= 0) b // a <= b <= c + else if (ac <= 0) c // a <= c < b + else a // c < a <= b + } else { + if (ac <= 0) a // b < a <= c + else if (bc <= 0) c // b <= c < a + else b // c < b < a + } } /**