https://kotlinlang.org logo
Title
s

Smallville7123

04/18/2019, 7:55 PM
a
for
loop is faster than a
forEach
expression right?
k

karelpeeters

04/18/2019, 7:56 PM
foreach
is often
inline
, and then it yields the same exact bytecode.
👍 2
s

Smallville7123

04/18/2019, 7:58 PM
oh
l

louiscad

04/18/2019, 8:21 PM
forEach and for (e in list) both allocate an iterator
Only for loop that uses a manual index doesn't
In Splitties Collections, there's a
forEachByIndex
extension for
List
k

karelpeeters

04/18/2019, 8:23 PM
You lose the concurrentmodification safety then.
t

tseisel

04/18/2019, 9:02 PM
This question sounds like premature optimization to me. Even if those two were not identical in performance, I suggest that you use the most readable for you (and your team). Also, if you need to
break
or
continue
, use the
for
loop as it is not supported in
forEach
.
p

pablisco

04/18/2019, 9:31 PM
I agree on the early optimization aspect. (Importance is relative to the application) However it's one order of magnitude slower(in milliseconds). Not sure why
forEachIndexed
is not as inefficient though 🤔 https://try.kotlinlang.org/#/UserProjects/eeg4jaic5jrlb4utcb6269lsfh/kifrboelan2267bgrijt0lg6o5
k

karelpeeters

04/18/2019, 9:32 PM
That a terrible way to banchmark stuff on the JVM.
For example, try changing the order and you'll see the results are completely different.
s

Smallville7123

04/18/2019, 10:05 PM
o.o
p

pablisco

04/18/2019, 10:21 PM
Yeah, that is true... I need to do a proper benchmark to verify 😅
Ok, I made a (slightly) better benchmark: https://pablisco.com/kotlin-benchmarks/ Here is the code: https://github.com/pablisco/kotlin-benchmarks I’m using
Single Shot
because it’s run on Travis. I say slightly better because the margin or error on it is wild. Not sure why 🤔
k

karelpeeters

04/18/2019, 11:07 PM
Those margins 🤔 . I can't imagine any reason why indexed would be faster then normal.
Also shouldn't the JVM realize the loop is empty and optimize it away? Maybe that's what happening?
p

pablisco

04/18/2019, 11:08 PM
I think that JMH removes such optimisations AFAIK
l

louiscad

04/19/2019, 6:00 AM
Allocating an iterator is always more costly than just using an index, and on Dalvik VM (Android 4.x), the cost usually results in significantly poorer performance, often dropping frames when done during animations or user interactions.
k

karelpeeters

04/19/2019, 8:07 AM
Dropping frames just from an iterator? That can't be right, surely there are hundreds of iterators created every second on Android, no? Also, Iterators are just small objects, I'm sure the VM can handle creating those?
2
(I'm asking, I'm not super familiar with Android development)
p

pablisco

04/19/2019, 8:12 AM
@karelpeeters It is significant if done inside a draw method in a view for example. It runs 60 times per second (or 120 on faster displays). On a slow device it definitely noticeable.
When dealing with drawing usually we have 16ms to do the whole frame, so any allocation creeps into that limit.
It just hit me that my benchmark uses
for(in)
which also creates an iterator. I'll add a case for an old fashion for loop. Although, at 10k iterations it shouldn't make much difference.
k

karelpeeters

04/19/2019, 8:15 AM
Interesting, thanks!
p

pablisco

04/19/2019, 8:17 AM
Also, this applies to running things on the main thread (for the UI). On background threads we can be more lenient and I tend to go for more readability 😅
g

gildor

04/19/2019, 3:49 PM
tho it make sense to optimize in onDraw method of course (which is still quite specific case), I don’t think that you will get dropping frames because of iterator instance creation, even 60 small objects per second shouldn’t cause any significant pressure on GC even on slow devices, most probably if you really do this in onDraw you have other expensive things
I don’t want to say that optimize allocations onDraw or other hot methods doesn’t make sense, if you know about some allocation, better to avoid it, but say that you will get dropping frames just because of iterator is exactly case of premature optimization #enumsmatter
l

louiscad

04/19/2019, 5:47 PM
Enums are allocated once. Iterator are allocated on each iteration. Before ART (5.0+), it is known to cause a significant GC pressure, so it's not premature optimization, but just making a decent UX for users. Maybe when all apps are as smooth as real-life on all devices in use, I'll reconsider, but we're not there, so I keep optimizing for onDraw and other hot spots, and it's not making code any less readable thanks to inline higher order functions.
p

pablisco

04/19/2019, 9:27 PM
I think the issue with using
forEach
is not the iterator allocation but rather that each iteration it's making a function call to the closure lambda where as in a traditional for loop it doesn't. Again it can be an early optimization since it only has an effect if we have thousands of elements. It's something to be mindful about, but always better to optimise if we observe adverse effects. I always like to test apps on old and low power devices to make sure 😁
g

gildor

04/20/2019, 12:27 AM
Yes, allocated on each iteration, I'm comparing with enums not because of type of allocation, but because of such sentences:
Known to cause a significant GC pressure
Sounds like a dogma than a real case. Again, I agree that it make sense to use loop with onDraw, but it's still very specific case and we usually care about it just because we know that you should be careful on such hot methods, but replacing iterator around your code base is exactly premature optimization
each iteration it's making a function call to the closure lambda where as in a traditional for loop it doesn't.
It's incorrect for standard Kotlin forEach implementation for collection. It is inlined and after compilation you have just loop using iterator
l

louiscad

04/20/2019, 7:10 AM
Well, it's not dogma or something, but just facts that not allocating is cheaper if you don't replace it with something more expensive. Also, allocating an iterator is useless for immutable lists, or lists that you're certain you don't modify concurrently. That's why Anko commons and Splitties collections have a forEachByIndex function that doesn't use an iterator. I don't care about the premature optimization dogma if you want my opinion. All I care about is use case, readability (maintainability) and efficiency.
p

pablisco

04/20/2019, 9:06 AM
@gildor I'll have to check the decompiled code again after the weekend. I'm sure it was making an invocation to the lambda 😅
g

gildor

04/20/2019, 9:09 AM
Are you sure that you are not talking about forEach on sequence?
p

pablisco

04/20/2019, 9:15 AM
I'm sure I'm not talking about sequences yeah. Haven't look at them in this case as they are completely different
I think that you are right @gildor about the inline as it's not defined as
noinline
. However, I'm not sure why I'm getting such unreliable results on this one 🤔 https://pablisco.com/kotlin-benchmarks/
g

gildor

04/21/2019, 12:32 AM
@pablisco I checked your benchmark and results make perfect sense.
forEachIndexed
is Kotlin stdlib function that create one more additional object with index and value, Louis above talked about own custom loop, implemented using iteration by index instead of iterator, not about this stdlib function Something like this:
inline fun <T> List<T>.forEachByIndex(block: (T) -> Unit) {
    for (i in 0 until size) {
        block(this[i])
    }
}
also such extension can be implemented only for List or Array, so your test with range wouldn’t work for this case.
l

louiscad

04/21/2019, 8:09 AM
Here's the implementation where I check eagerly for size changes that may lead to out of bounds later on in the iteration (not as "safe" as iterator, but better than nothing to spot programmer errors): https://github.com/LouisCAD/Splitties/blob/61a4035ba3b18270dbfb0828436b5045bff44919/modules/collections/src/commonMain/kotlin/splitties/collections/Lists.kt
Iterating an
Array
doesn't allocate an iterator because an Array can't change its size and doesn't count modifications.
s

Smallville7123

04/21/2019, 9:36 AM
rip i started a discussion that may not be answerable x.x
continues to watch how this plays out : p
p

pablisco

04/21/2019, 12:19 PM
@Smallville7123 in theory
for(in)
is exactly the same as
forEach
. In practice, like many things in our field, it depends 😅
g

gildor

04/21/2019, 3:23 PM
Your original question was answered in the first few messages