Kotlin: Why is Sequence more performant in this example?
up vote
2
down vote
favorite
Currently, I am looking into Kotlin and have a question about Sequences vs. Collections.
I read a blog post about this topic and there you can find this code snippets:
List implementation:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
measure
list
.filter it % 3 == 0
.average()
// 8644 ms
Sequence implementation:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
measure
sequence
.filter it % 3 == 0
.average()
// 822 ms
The point here is that the Sequence implementation is about 10x faster.
However, I do not really understand WHY that is. I know that with a Sequence, you do "lazy evaluation", but I cannot find any reason why that helps reducing the processing in this example.
However, here I know why a Sequence is generally faster:
val result = sequenceOf("a", "b", "c")
.map
println("map: $it")
it.toUpperCase()
.any
println("any: $it")
it.startsWith("B")
Because with a Sequence you process the data "vertically", when the first element starts with "B", you don't have to map for the rest of the elements. It makes sense here.
So, why is it also faster in the first example?
kotlin
add a comment |
up vote
2
down vote
favorite
Currently, I am looking into Kotlin and have a question about Sequences vs. Collections.
I read a blog post about this topic and there you can find this code snippets:
List implementation:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
measure
list
.filter it % 3 == 0
.average()
// 8644 ms
Sequence implementation:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
measure
sequence
.filter it % 3 == 0
.average()
// 822 ms
The point here is that the Sequence implementation is about 10x faster.
However, I do not really understand WHY that is. I know that with a Sequence, you do "lazy evaluation", but I cannot find any reason why that helps reducing the processing in this example.
However, here I know why a Sequence is generally faster:
val result = sequenceOf("a", "b", "c")
.map
println("map: $it")
it.toUpperCase()
.any
println("any: $it")
it.startsWith("B")
Because with a Sequence you process the data "vertically", when the first element starts with "B", you don't have to map for the rest of the elements. It makes sense here.
So, why is it also faster in the first example?
kotlin
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
Currently, I am looking into Kotlin and have a question about Sequences vs. Collections.
I read a blog post about this topic and there you can find this code snippets:
List implementation:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
measure
list
.filter it % 3 == 0
.average()
// 8644 ms
Sequence implementation:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
measure
sequence
.filter it % 3 == 0
.average()
// 822 ms
The point here is that the Sequence implementation is about 10x faster.
However, I do not really understand WHY that is. I know that with a Sequence, you do "lazy evaluation", but I cannot find any reason why that helps reducing the processing in this example.
However, here I know why a Sequence is generally faster:
val result = sequenceOf("a", "b", "c")
.map
println("map: $it")
it.toUpperCase()
.any
println("any: $it")
it.startsWith("B")
Because with a Sequence you process the data "vertically", when the first element starts with "B", you don't have to map for the rest of the elements. It makes sense here.
So, why is it also faster in the first example?
kotlin
Currently, I am looking into Kotlin and have a question about Sequences vs. Collections.
I read a blog post about this topic and there you can find this code snippets:
List implementation:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
measure
list
.filter it % 3 == 0
.average()
// 8644 ms
Sequence implementation:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
measure
sequence
.filter it % 3 == 0
.average()
// 822 ms
The point here is that the Sequence implementation is about 10x faster.
However, I do not really understand WHY that is. I know that with a Sequence, you do "lazy evaluation", but I cannot find any reason why that helps reducing the processing in this example.
However, here I know why a Sequence is generally faster:
val result = sequenceOf("a", "b", "c")
.map
println("map: $it")
it.toUpperCase()
.any
println("any: $it")
it.startsWith("B")
Because with a Sequence you process the data "vertically", when the first element starts with "B", you don't have to map for the rest of the elements. It makes sense here.
So, why is it also faster in the first example?
kotlin
kotlin
asked 2 days ago
Aliquis
362212
362212
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
up vote
5
down vote
accepted
Let's look at what those two implementations are actually doing:
The List implementation first creates a List in memory with 50 million elements. This will take a bare minimum of 200MB, since an integer takes 4 bytes.
(In fact, it's probably far more than that. As Alexey Romanov pointed out, since it's a generic
List
implementation and not anIntList
, it won't be storing the integers directly, but will be ‘boxing’ them — storing references toInt
objects. Each reference will be 8 or 16 bytes, and eachInt
will probably take 16, giving over 1GB. Also, depending how the List gets created, it might start with a small array and keep creating larger and larger ones as the list grows, copying all the values across each time.)Then it has to read all the values back from the list, filter them, and create another list in memory.
Finally, it has to read all those values back in again, to calculate the average.
The Sequence implementation, on the other hand, doesn't have to store anything! It simply generates the values in order, and as it does each one it checks whether it's divisible by 3 and if so includes it in the average.
(That's pretty much how you'd do it if you were implementing it ‘by hand’.)
You can see that in addition to the divisibility checking and average calculation, the List implementation is doing a massive amount of memory access, which will take a lot of time. That's the main reason it's far slower than the Sequence version, which doesn't!
Seeing this, you might ask why we don't use Sequences everywhere… But this is a fairly extreme example. Setting up and then iterating the Sequence has some overhead of its own, and for smallish lists that can outweigh the memory overhead. So Sequences only have a clear advantage in cases when the lists are very large, are processed strictly in order, there are several intermediate steps, and/or many items are filtered out along the way (especially if the Sequence is infinite!).
In my experience, those conditions don't occur very often. But this question shows how important it is to recognise them when they do!
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
add a comment |
up vote
2
down vote
Leveraging lazy-evaluation allows avoiding the creation of intermediate objects that are irrelevant from the point of the end goal.
Also, the benchmarking method used in the mentioned article is not super accurate. Try to repeat the experiment with JMH.
Initial code produces a list containing 50_000_000 objects:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
then iterates through it and creates another list containing a subset of its elements:
.filter it % 3 == 0
... and then proceeds with calculating the average:
.average()
Using sequences allows you to avoid doing all those intermediate steps. The below code doesn't produce 50_000_000 elements, it's just a representation of that 1...50_000_000 sequence:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
adding a filtering to it doesn't trigger the calculation itself as well but derives a new sequence from the existing one (3, 6, 9...):
.filter it % 3 == 0
and eventually, a terminal operation is called that triggers the evaluation of the sequence and the actual calculation:
.average()
Some relevant reading:
Kotlin: Beware of Java Stream API Habits
Kotlin Collections API Performance Antipatterns
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
5
down vote
accepted
Let's look at what those two implementations are actually doing:
The List implementation first creates a List in memory with 50 million elements. This will take a bare minimum of 200MB, since an integer takes 4 bytes.
(In fact, it's probably far more than that. As Alexey Romanov pointed out, since it's a generic
List
implementation and not anIntList
, it won't be storing the integers directly, but will be ‘boxing’ them — storing references toInt
objects. Each reference will be 8 or 16 bytes, and eachInt
will probably take 16, giving over 1GB. Also, depending how the List gets created, it might start with a small array and keep creating larger and larger ones as the list grows, copying all the values across each time.)Then it has to read all the values back from the list, filter them, and create another list in memory.
Finally, it has to read all those values back in again, to calculate the average.
The Sequence implementation, on the other hand, doesn't have to store anything! It simply generates the values in order, and as it does each one it checks whether it's divisible by 3 and if so includes it in the average.
(That's pretty much how you'd do it if you were implementing it ‘by hand’.)
You can see that in addition to the divisibility checking and average calculation, the List implementation is doing a massive amount of memory access, which will take a lot of time. That's the main reason it's far slower than the Sequence version, which doesn't!
Seeing this, you might ask why we don't use Sequences everywhere… But this is a fairly extreme example. Setting up and then iterating the Sequence has some overhead of its own, and for smallish lists that can outweigh the memory overhead. So Sequences only have a clear advantage in cases when the lists are very large, are processed strictly in order, there are several intermediate steps, and/or many items are filtered out along the way (especially if the Sequence is infinite!).
In my experience, those conditions don't occur very often. But this question shows how important it is to recognise them when they do!
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
add a comment |
up vote
5
down vote
accepted
Let's look at what those two implementations are actually doing:
The List implementation first creates a List in memory with 50 million elements. This will take a bare minimum of 200MB, since an integer takes 4 bytes.
(In fact, it's probably far more than that. As Alexey Romanov pointed out, since it's a generic
List
implementation and not anIntList
, it won't be storing the integers directly, but will be ‘boxing’ them — storing references toInt
objects. Each reference will be 8 or 16 bytes, and eachInt
will probably take 16, giving over 1GB. Also, depending how the List gets created, it might start with a small array and keep creating larger and larger ones as the list grows, copying all the values across each time.)Then it has to read all the values back from the list, filter them, and create another list in memory.
Finally, it has to read all those values back in again, to calculate the average.
The Sequence implementation, on the other hand, doesn't have to store anything! It simply generates the values in order, and as it does each one it checks whether it's divisible by 3 and if so includes it in the average.
(That's pretty much how you'd do it if you were implementing it ‘by hand’.)
You can see that in addition to the divisibility checking and average calculation, the List implementation is doing a massive amount of memory access, which will take a lot of time. That's the main reason it's far slower than the Sequence version, which doesn't!
Seeing this, you might ask why we don't use Sequences everywhere… But this is a fairly extreme example. Setting up and then iterating the Sequence has some overhead of its own, and for smallish lists that can outweigh the memory overhead. So Sequences only have a clear advantage in cases when the lists are very large, are processed strictly in order, there are several intermediate steps, and/or many items are filtered out along the way (especially if the Sequence is infinite!).
In my experience, those conditions don't occur very often. But this question shows how important it is to recognise them when they do!
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
add a comment |
up vote
5
down vote
accepted
up vote
5
down vote
accepted
Let's look at what those two implementations are actually doing:
The List implementation first creates a List in memory with 50 million elements. This will take a bare minimum of 200MB, since an integer takes 4 bytes.
(In fact, it's probably far more than that. As Alexey Romanov pointed out, since it's a generic
List
implementation and not anIntList
, it won't be storing the integers directly, but will be ‘boxing’ them — storing references toInt
objects. Each reference will be 8 or 16 bytes, and eachInt
will probably take 16, giving over 1GB. Also, depending how the List gets created, it might start with a small array and keep creating larger and larger ones as the list grows, copying all the values across each time.)Then it has to read all the values back from the list, filter them, and create another list in memory.
Finally, it has to read all those values back in again, to calculate the average.
The Sequence implementation, on the other hand, doesn't have to store anything! It simply generates the values in order, and as it does each one it checks whether it's divisible by 3 and if so includes it in the average.
(That's pretty much how you'd do it if you were implementing it ‘by hand’.)
You can see that in addition to the divisibility checking and average calculation, the List implementation is doing a massive amount of memory access, which will take a lot of time. That's the main reason it's far slower than the Sequence version, which doesn't!
Seeing this, you might ask why we don't use Sequences everywhere… But this is a fairly extreme example. Setting up and then iterating the Sequence has some overhead of its own, and for smallish lists that can outweigh the memory overhead. So Sequences only have a clear advantage in cases when the lists are very large, are processed strictly in order, there are several intermediate steps, and/or many items are filtered out along the way (especially if the Sequence is infinite!).
In my experience, those conditions don't occur very often. But this question shows how important it is to recognise them when they do!
Let's look at what those two implementations are actually doing:
The List implementation first creates a List in memory with 50 million elements. This will take a bare minimum of 200MB, since an integer takes 4 bytes.
(In fact, it's probably far more than that. As Alexey Romanov pointed out, since it's a generic
List
implementation and not anIntList
, it won't be storing the integers directly, but will be ‘boxing’ them — storing references toInt
objects. Each reference will be 8 or 16 bytes, and eachInt
will probably take 16, giving over 1GB. Also, depending how the List gets created, it might start with a small array and keep creating larger and larger ones as the list grows, copying all the values across each time.)Then it has to read all the values back from the list, filter them, and create another list in memory.
Finally, it has to read all those values back in again, to calculate the average.
The Sequence implementation, on the other hand, doesn't have to store anything! It simply generates the values in order, and as it does each one it checks whether it's divisible by 3 and if so includes it in the average.
(That's pretty much how you'd do it if you were implementing it ‘by hand’.)
You can see that in addition to the divisibility checking and average calculation, the List implementation is doing a massive amount of memory access, which will take a lot of time. That's the main reason it's far slower than the Sequence version, which doesn't!
Seeing this, you might ask why we don't use Sequences everywhere… But this is a fairly extreme example. Setting up and then iterating the Sequence has some overhead of its own, and for smallish lists that can outweigh the memory overhead. So Sequences only have a clear advantage in cases when the lists are very large, are processed strictly in order, there are several intermediate steps, and/or many items are filtered out along the way (especially if the Sequence is infinite!).
In my experience, those conditions don't occur very often. But this question shows how important it is to recognise them when they do!
edited 2 days ago
answered 2 days ago
gidds
3363
3363
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
add a comment |
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
Good answer overall, but I'd add two things: 1. Sequences are also much slower (if usable at all) if you need to do anything other than process each element in order. 2. The initial list is going to be much larger than 200MB because integers need to be boxed, see stackoverflow.com/questions/8419860/….
– Alexey Romanov
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
@AlexeyRomanov: good points, thanks! I should have spotted that the ints will be boxed. I'll update my answer.
– gidds
2 days ago
add a comment |
up vote
2
down vote
Leveraging lazy-evaluation allows avoiding the creation of intermediate objects that are irrelevant from the point of the end goal.
Also, the benchmarking method used in the mentioned article is not super accurate. Try to repeat the experiment with JMH.
Initial code produces a list containing 50_000_000 objects:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
then iterates through it and creates another list containing a subset of its elements:
.filter it % 3 == 0
... and then proceeds with calculating the average:
.average()
Using sequences allows you to avoid doing all those intermediate steps. The below code doesn't produce 50_000_000 elements, it's just a representation of that 1...50_000_000 sequence:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
adding a filtering to it doesn't trigger the calculation itself as well but derives a new sequence from the existing one (3, 6, 9...):
.filter it % 3 == 0
and eventually, a terminal operation is called that triggers the evaluation of the sequence and the actual calculation:
.average()
Some relevant reading:
Kotlin: Beware of Java Stream API Habits
Kotlin Collections API Performance Antipatterns
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
add a comment |
up vote
2
down vote
Leveraging lazy-evaluation allows avoiding the creation of intermediate objects that are irrelevant from the point of the end goal.
Also, the benchmarking method used in the mentioned article is not super accurate. Try to repeat the experiment with JMH.
Initial code produces a list containing 50_000_000 objects:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
then iterates through it and creates another list containing a subset of its elements:
.filter it % 3 == 0
... and then proceeds with calculating the average:
.average()
Using sequences allows you to avoid doing all those intermediate steps. The below code doesn't produce 50_000_000 elements, it's just a representation of that 1...50_000_000 sequence:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
adding a filtering to it doesn't trigger the calculation itself as well but derives a new sequence from the existing one (3, 6, 9...):
.filter it % 3 == 0
and eventually, a terminal operation is called that triggers the evaluation of the sequence and the actual calculation:
.average()
Some relevant reading:
Kotlin: Beware of Java Stream API Habits
Kotlin Collections API Performance Antipatterns
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
add a comment |
up vote
2
down vote
up vote
2
down vote
Leveraging lazy-evaluation allows avoiding the creation of intermediate objects that are irrelevant from the point of the end goal.
Also, the benchmarking method used in the mentioned article is not super accurate. Try to repeat the experiment with JMH.
Initial code produces a list containing 50_000_000 objects:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
then iterates through it and creates another list containing a subset of its elements:
.filter it % 3 == 0
... and then proceeds with calculating the average:
.average()
Using sequences allows you to avoid doing all those intermediate steps. The below code doesn't produce 50_000_000 elements, it's just a representation of that 1...50_000_000 sequence:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
adding a filtering to it doesn't trigger the calculation itself as well but derives a new sequence from the existing one (3, 6, 9...):
.filter it % 3 == 0
and eventually, a terminal operation is called that triggers the evaluation of the sequence and the actual calculation:
.average()
Some relevant reading:
Kotlin: Beware of Java Stream API Habits
Kotlin Collections API Performance Antipatterns
Leveraging lazy-evaluation allows avoiding the creation of intermediate objects that are irrelevant from the point of the end goal.
Also, the benchmarking method used in the mentioned article is not super accurate. Try to repeat the experiment with JMH.
Initial code produces a list containing 50_000_000 objects:
val list = generateSequence(1) it + 1
.take(50_000_000)
.toList()
then iterates through it and creates another list containing a subset of its elements:
.filter it % 3 == 0
... and then proceeds with calculating the average:
.average()
Using sequences allows you to avoid doing all those intermediate steps. The below code doesn't produce 50_000_000 elements, it's just a representation of that 1...50_000_000 sequence:
val sequence = generateSequence(1) it + 1
.take(50_000_000)
adding a filtering to it doesn't trigger the calculation itself as well but derives a new sequence from the existing one (3, 6, 9...):
.filter it % 3 == 0
and eventually, a terminal operation is called that triggers the evaluation of the sequence and the actual calculation:
.average()
Some relevant reading:
Kotlin: Beware of Java Stream API Habits
Kotlin Collections API Performance Antipatterns
answered 2 days ago
Grzegorz Piwowarek
7,11042560
7,11042560
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
add a comment |
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
Well, compared to the second example in the OP, about the same number of operations are needed for filtering and then calculating the average when using a sequence or normal collection. So, the reason why it's more performant is because no intermediate collections must be created, right?
– Aliquis
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
@Aliquis it's not the same number of operations - in the first example, you need to iterate 50_000_000 times to create a filtered list and then proceed with calculating the average (which involves more iteration). In the second one, you just go straight for the average (you iterate the sequence once with filtering applied) without creating any redundant objects + possibly avoid autoboxing of ints
– Grzegorz Piwowarek
2 days ago
add a comment |
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53237853%2fkotlin-why-is-sequence-more-performant-in-this-example%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password