From 1f273850242ecd6d1622dfff8710d3aeab72af44 Mon Sep 17 00:00:00 2001 From: Alexis Manin Date: Fri, 28 Mar 2025 09:59:24 +0100 Subject: [PATCH 1/3] KT-75801: Optimize stdlib Array.flatten() function 1. Previous version used ArrayList.addall method to copy inner arrays into destination list, which caused extra copies. 2. Add special cases to return early on empty or single element array --- .../stdlib/src/kotlin/collections/Arrays.kt | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/libraries/stdlib/src/kotlin/collections/Arrays.kt b/libraries/stdlib/src/kotlin/collections/Arrays.kt index f789c74b42b19..b6c627ef49596 100644 --- a/libraries/stdlib/src/kotlin/collections/Arrays.kt +++ b/libraries/stdlib/src/kotlin/collections/Arrays.kt @@ -17,11 +17,23 @@ import kotlin.contracts.* * @sample samples.collections.Arrays.Transformations.flattenArray */ public fun Array>.flatten(): List { - val result = ArrayList(sumOf { it.size }) - for (element in this) { - result.addAll(element) + if (isEmpty()) return emptyList() + + val totalSizeLong = sumOf { it.size.toLong() } + if (totalSizeLong == 0L) return emptyList() + require(totalSizeLong <= Int.MAX_VALUE.toLong()) { + "Sum of all arrays overflow maximum array capacity (of Int.MAX_VALUE)" + } + + val outputArray = arrayOfNulls(totalSizeLong.toInt()) + var offset = 0 + for (innerArray in this) { + innerArray.copyInto(outputArray, offset) + offset += innerArray.size } - return result + + @Suppress("UNCHECKED_CAST") + return outputArray.asList() as List } /** From b27f970bc57c0c7317b42dd03b788865cafb7ec0 Mon Sep 17 00:00:00 2001 From: Alexis Manin Date: Thu, 3 Apr 2025 22:16:28 +0200 Subject: [PATCH 2/3] KT-75801: Optimize stdlib take, takeLast and takeWhile functions Avoid copying array elements using a loop. --- .../stdlib/common/src/generated/_Arrays.kt | 26 ++++--------- .../src/templates/Filtering.kt | 39 ++++++++++++++++++- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 9aafae332b447..fe8d9a43365b4 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -4806,14 +4806,7 @@ public fun Array.take(n: Int): List { if (n == 0) return emptyList() if (n >= size) return toList() if (n == 1) return listOf(this[0]) - var count = 0 - val list = ArrayList(n) - for (item in this) { - list.add(item) - if (++count == n) - break - } - return list + return copyOfRange(0, n).asList() } /** @@ -5005,10 +4998,7 @@ public fun Array.takeLast(n: Int): List { val size = size if (n >= size) return toList() if (n == 1) return listOf(this[size - 1]) - val list = ArrayList(n) - for (index in size - n until size) - list.add(this[index]) - return list + return copyOfRange(size - n, size).asList() } /** @@ -5295,13 +5285,11 @@ public inline fun CharArray.takeLastWhile(predicate: (Char) -> Boolean): List Array.takeWhile(predicate: (T) -> Boolean): List { - val list = ArrayList() - for (item in this) { - if (!predicate(item)) - break - list.add(item) - } - return list + var i = 0 + while (i < size && predicate(this[i])) i++ + return if (i == 0) emptyList() + else if (i == 1) listOf(this[0]) + else copyOfRange(0, i).asList() } /** diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt index 840191c5f86cb..9e333e1b94af0 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt @@ -192,7 +192,7 @@ object Filtering : TemplateGroupBase() { """ } - body(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned) { + body(ArraysOfPrimitives, ArraysOfUnsigned) { """ require(n >= 0) { "Requested element count $n is less than zero." } if (n == 0) return emptyList() @@ -208,6 +208,17 @@ object Filtering : TemplateGroupBase() { return list """ } + + // For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801) + body(ArraysOfObjects) { + """ + require(n >= 0) { "Requested element count $n is less than zero." } + if (n == 0) return emptyList() + if (n >= size) return toList() + if (n == 1) return listOf(this[0]) + return copyOfRange(0, n).asList() + """ + } } val f_dropLast = fn("dropLast(n: Int)") { @@ -270,7 +281,7 @@ object Filtering : TemplateGroupBase() { """ } - body(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned) { + body(ArraysOfPrimitives, ArraysOfUnsigned) { """ require(n >= 0) { "Requested element count $n is less than zero." } if (n == 0) return emptyList() @@ -284,6 +295,19 @@ object Filtering : TemplateGroupBase() { return list """ } + + // For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801) + body(ArraysOfObjects) { + """ + require(n >= 0) { "Requested element count $n is less than zero." } + if (n == 0) return emptyList() + val size = size + if (n >= size) return toList() + if (n == 1) return listOf(this[size - 1]) + return copyOfRange(size - n, size).asList() + """ + } + body(Lists) { """ require(n >= 0) { "Requested element count $n is less than zero." } @@ -413,6 +437,17 @@ object Filtering : TemplateGroupBase() { returns("Sequence") body { """return TakeWhileSequence(this, predicate)""" } } + + // For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801) + body(ArraysOfObjects) { + """ + var i = 0 + while (i < ${f.code.size} && predicate(this[i])) i++ + return if (i == 0) emptyList() + else if (i == 1) listOf(this[0]) + else copyOfRange(0, i).asList() + """ + } } val f_dropLastWhile = fn("dropLastWhile(predicate: (T) -> Boolean)") { From a27bac72fd77dfda85040802468dc82e91701297 Mon Sep 17 00:00:00 2001 From: Alexis Manin Date: Thu, 3 Apr 2025 22:30:46 +0200 Subject: [PATCH 3/3] KT-75801: Optimize stdlib Array.toList() function Replace inner `toMutableList()` call with `copyOf().asList()`. This change reduce number of array copies from two to one. --- libraries/stdlib/common/src/generated/_Arrays.kt | 2 +- .../kotlin-stdlib-gen/src/templates/Snapshots.kt | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index fe8d9a43365b4..6d97161832a1a 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -9826,7 +9826,7 @@ public fun Array.toList(): List { return when (size) { 0 -> emptyList() 1 -> listOf(this[0]) - else -> this.toMutableList() + else -> copyOf().asList() } } diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt index 3f58410a32659..621f0ec6b1694 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt @@ -173,7 +173,7 @@ object Snapshots : TemplateGroupBase() { return this.toMutableList().optimizeReadOnlyList() """ } - body(CharSequences, ArraysOfPrimitives, ArraysOfObjects) { + body(CharSequences, ArraysOfPrimitives) { """ return when (${f.code.size}) { 0 -> emptyList() @@ -182,6 +182,16 @@ object Snapshots : TemplateGroupBase() { } """ } + // For object array, ensure a single array copy instead of delegating to `toMutableList`, which can cause two copies (see KT-75801) + body(ArraysOfObjects) { + """ + return when (${f.code.size}) { + 0 -> emptyList() + 1 -> listOf(this[0]) + else -> copyOf().asList() + } + """ + } body(Sequences) { optimizedSequenceToCollection("emptyList", "listOf", "ArrayList") } specialFor(Maps) { doc { "Returns a [List] containing all key-value pairs." }