Skip to content

Commit 04ce680

Browse files
alexismaninSpace Team
authored andcommitted
KT-75801: optimize functions converting object array to list
This contains two different cases of optimization: * Array<T>.(take|takeLast|takeWhile) functions : replace loop copy with copyOfRange().asList() * Array<T>.toList() and Array<Array<T>>.flatten() : force using direct array copy instead of delegating to mutable list. Primitive array functions were not modified, because of unboxing. When the optimization is applied to primitive arrays, the initial copy might indeed get faster, but then, unboxing would be delayed on List.get() calls, therefore adding performance penalty on the consumer side. Benchmarks being used: - original set of benchmarks: https://github.com/alexismanin/kt-benchmark-array-to-list - later validated on: https://github.com/fzhinkin/kt-benchmark-array-to-list/tree/run-filter-on-pre-allocated-list ^KT-75801 Fixed ^KT-82033 Fixed Initial GH PR: #5426 Merge-request: KT-MR-21026 Merged-by: Filipp Zhinkin <[email protected]>
1 parent 8131c51 commit 04ce680

File tree

4 files changed

+70
-27
lines changed

4 files changed

+70
-27
lines changed

libraries/stdlib/common/src/generated/_Arrays.kt

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4944,14 +4944,7 @@ public fun <T> Array<out T>.take(n: Int): List<T> {
49444944
if (n == 0) return emptyList()
49454945
if (n >= size) return toList()
49464946
if (n == 1) return listOf(this[0])
4947-
var count = 0
4948-
val list = ArrayList<T>(n)
4949-
for (item in this) {
4950-
list.add(item)
4951-
if (++count == n)
4952-
break
4953-
}
4954-
return list
4947+
return copyOfRange(0, n).asList()
49554948
}
49564949

49574950
/**
@@ -5143,10 +5136,7 @@ public fun <T> Array<out T>.takeLast(n: Int): List<T> {
51435136
val size = size
51445137
if (n >= size) return toList()
51455138
if (n == 1) return listOf(this[size - 1])
5146-
val list = ArrayList<T>(n)
5147-
for (index in size - n until size)
5148-
list.add(this[index])
5149-
return list
5139+
return copyOfRange(size - n, size).asList()
51505140
}
51515141

51525142
/**
@@ -5433,13 +5423,11 @@ public inline fun CharArray.takeLastWhile(predicate: (Char) -> Boolean): List<Ch
54335423
* @sample samples.collections.Collections.Transformations.take
54345424
*/
54355425
public inline fun <T> Array<out T>.takeWhile(predicate: (T) -> Boolean): List<T> {
5436-
val list = ArrayList<T>()
5437-
for (item in this) {
5438-
if (!predicate(item))
5439-
break
5440-
list.add(item)
5441-
}
5442-
return list
5426+
var i = 0
5427+
while (i < size && predicate(this[i])) i++
5428+
return if (i == 0) emptyList()
5429+
else if (i == 1) listOf(this[0])
5430+
else copyOfRange(0, i).asList()
54435431
}
54445432

54455433
/**
@@ -10277,7 +10265,7 @@ public fun <T> Array<out T>.toList(): List<T> {
1027710265
return when (size) {
1027810266
0 -> emptyList()
1027910267
1 -> listOf(this[0])
10280-
else -> this.toMutableList()
10268+
else -> copyOf().asList()
1028110269
}
1028210270
}
1028310271

libraries/stdlib/src/kotlin/collections/Arrays.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,21 @@ import kotlin.contracts.*
1717
* @sample samples.collections.Arrays.Transformations.flattenArray
1818
*/
1919
public fun <T> Array<out Array<out T>>.flatten(): List<T> {
20-
val result = ArrayList<T>(sumOf { it.size })
21-
for (element in this) {
22-
result.addAll(element)
20+
val totalSizeLong = sumOf { it.size.toLong() }
21+
if (totalSizeLong == 0L) return emptyList()
22+
require(totalSizeLong <= Int.MAX_VALUE.toLong()) {
23+
"Sum of all arrays lengths ($totalSizeLong) exceeds maximum list size (${Int.MAX_VALUE})"
2324
}
24-
return result
25+
26+
val outputArray = arrayOfNulls<Any?>(totalSizeLong.toInt())
27+
var offset = 0
28+
for (innerArray in this) {
29+
innerArray.copyInto(outputArray, offset)
30+
offset += innerArray.size
31+
}
32+
33+
@Suppress("UNCHECKED_CAST")
34+
return outputArray.asList() as List<T>
2535
}
2636

2737
/**

libraries/tools/kotlin-stdlib-gen/src/templates/Filtering.kt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ object Filtering : TemplateGroupBase() {
192192
"""
193193
}
194194

195-
body(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned) {
195+
body(ArraysOfPrimitives, ArraysOfUnsigned) {
196196
"""
197197
require(n >= 0) { "Requested element count $n is less than zero." }
198198
if (n == 0) return emptyList()
@@ -208,6 +208,17 @@ object Filtering : TemplateGroupBase() {
208208
return list
209209
"""
210210
}
211+
212+
// For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801)
213+
body(ArraysOfObjects) {
214+
"""
215+
require(n >= 0) { "Requested element count $n is less than zero." }
216+
if (n == 0) return emptyList()
217+
if (n >= size) return toList()
218+
if (n == 1) return listOf(this[0])
219+
return copyOfRange(0, n).asList()
220+
"""
221+
}
211222
}
212223

213224
val f_dropLast = fn("dropLast(n: Int)") {
@@ -270,7 +281,7 @@ object Filtering : TemplateGroupBase() {
270281
"""
271282
}
272283

273-
body(ArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned) {
284+
body(ArraysOfPrimitives, ArraysOfUnsigned) {
274285
"""
275286
require(n >= 0) { "Requested element count $n is less than zero." }
276287
if (n == 0) return emptyList()
@@ -284,6 +295,19 @@ object Filtering : TemplateGroupBase() {
284295
return list
285296
"""
286297
}
298+
299+
// For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801)
300+
body(ArraysOfObjects) {
301+
"""
302+
require(n >= 0) { "Requested element count $n is less than zero." }
303+
if (n == 0) return emptyList()
304+
val size = size
305+
if (n >= size) return toList()
306+
if (n == 1) return listOf(this[size - 1])
307+
return copyOfRange(size - n, size).asList()
308+
"""
309+
}
310+
287311
body(Lists) {
288312
"""
289313
require(n >= 0) { "Requested element count $n is less than zero." }
@@ -413,6 +437,17 @@ object Filtering : TemplateGroupBase() {
413437
returns("Sequence<T>")
414438
body { """return TakeWhileSequence(this, predicate)""" }
415439
}
440+
441+
// For object arrays, ensure a single array copy instead of copying using a loop (see KT-75801)
442+
body(ArraysOfObjects) {
443+
"""
444+
var i = 0
445+
while (i < ${f.code.size} && predicate(this[i])) i++
446+
return if (i == 0) emptyList()
447+
else if (i == 1) listOf(this[0])
448+
else copyOfRange(0, i).asList()
449+
"""
450+
}
416451
}
417452

418453
val f_dropLastWhile = fn("dropLastWhile(predicate: (T) -> Boolean)") {

libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ object Snapshots : TemplateGroupBase() {
174174
return this.toMutableList().optimizeReadOnlyList()
175175
"""
176176
}
177-
body(CharSequences, ArraysOfPrimitives, ArraysOfObjects) {
177+
body(CharSequences, ArraysOfPrimitives) {
178178
"""
179179
return when (${f.code.size}) {
180180
0 -> emptyList()
@@ -183,6 +183,16 @@ object Snapshots : TemplateGroupBase() {
183183
}
184184
"""
185185
}
186+
// For object array, ensure a single array copy instead of delegating to `toMutableList`, which can cause two copies (see KT-75801)
187+
body(ArraysOfObjects) {
188+
"""
189+
return when (${f.code.size}) {
190+
0 -> emptyList()
191+
1 -> listOf(this[0])
192+
else -> copyOf().asList()
193+
}
194+
"""
195+
}
186196
body(Sequences) { optimizedSequenceToCollection("emptyList", "listOf", "ArrayList") }
187197
specialFor(Maps) {
188198
doc { "Returns a [List] containing all key-value pairs." }

0 commit comments

Comments
 (0)