Array/List iteration without extra object allocations
I'm working on a game written in Kotlin and was looking into improving GC churn. One of the major sources of churn are for-loops called in the main game/rendering loops that result in the allocation of iterators.
Turning to the documentation, I found this paragraph:
A for loop over an array is compiled to an index-based loop that does not create an iterator object.
If you want to iterate through an array or a list with an index, you can do it this way:
for (i in array.indices)
print(array[i])
Note that this “iteration through a range” is compiled down to optimal implementation with no extra objects created.
https://kotlinlang.org/docs/reference/control-flow.html#for-loops
Is this really true? To verify, I took this simple Kotlin program and inspected the generated byte code:
fun main(args: Array<String>) {
val arr = arrayOf(1, 2, 3)
for (i in arr.indices) {
println(arr[i])
}
}
According to the quote above, this should not result in any objects allocated, but get compiled down to a good old pre-Java-5 style for-loop. However, what I got was this:
41: aload_1
42: checkcast #23 // class "[Ljava/lang/Object;"
45: invokestatic #31 // Method kotlin/collections/ArraysKt.getIndices:([Ljava/lang/Object;)Lkotlin/ranges/IntRange;
48: dup
49: invokevirtual #37 // Method kotlin/ranges/IntRange.getFirst:()I
52: istore_2
53: invokevirtual #40 // Method kotlin/ranges/IntRange.getLast:()I
56: istore_3
57: iload_2
58: iload_3
59: if_icmpgt 93
This looks to me as if a method called getIndices
is called that allocates a temporary IntRange
object to back up bounds checking in this loop. How is this an "optimal implementation" with "no extra objects created", or am I missing something?
UPDATE: So, after toying around a bit more and looking at the answers, the following appears to be true for Kotlin 1.0.2:
Arrays:
-
for (i in array.indices)
: range allocation -
for (i in 0..array.size)
: no allocation -
for (el in array)
: no allocation -
array.forEach
: no allocation
Collections:
-
for (i in coll.indices)
range allocation -
for (i in 0..coll.size)
: no allocation -
for (el in coll)
: iterator allocation -
coll.forEach
: iterator allocation
Solution 1:
To iterate an array without allocating extra objects you can use one of the following ways.
-
for
-loop
for (e in arr) {
println(e)
}
-
forEach
extension
arr.forEach {
println(it)
}
-
forEachIndexed
extension, if you need to know index of each element
arr.forEachIndexed { index, e ->
println("$e at $index")
}
Solution 2:
As far as I know the only allocation-less way to define a for
loop is
for (i in 0..count - 1)
All other forms lead to either a Range
allocation or an Iterator
allocation. Unfortunately, you cannot even define an effective reverse for
loop.
Solution 3:
Here is an example of preparing a list and iterate with index and value.
val list = arrayListOf("1", "11", "111")
for ((index, value) in list.withIndex()) {
println("$index: $value")
}
Output:
0:1
1:11
2:111
Also, following code works similar,
val simplearray = arrayOf(1, 2, 3, 4, 5)
for ((index, value) in simplearray.withIndex()) {
println("$index: $value")
}