Reputation: 4712
Currently, I am experimenting a bit with kotlin coroutines and I asked myself a question: Is there any significant performance gain when using coroutines? Let's look at some (theoretical) examples:
val myUnsortedList = listOf(1, 2, 5, 6, 3, 10, 100)
fun doStuffWithSorted(list: List<Int>) { ... }
// Without coroutine
val sortedList = myUnsortedList.sorted()
doStuffWithSorted(sortedList)
coroutineScope {
launch {
val sortedList = myUnsortedList.sorted()
doStuffWithSorted(sortedList)
}
}
fun doSomeHeavyOperations() { // doing heavy Operations but non blocking }
fun main() { doSomeHeavyOperations() }
//////////////// VERSUS //////////////////////
suspend fun doSomeHeavyOperations() { // doing heavy Operations but non blocking }
suspend fun main() {
coroutineScope {
launch {
doSomeHeavyOperations()
}
}
}
There are many more examples and maybe one of you could give some, where using coroutines is recommended. So my final question is (including the question above): When should it be considered to optimize the code performance with coroutines, and when is the expense bigger than the gained performance?
Upvotes: 3
Views: 1672
Reputation: 8315
Coroutines are primarily a tool for computations that involve a lot of waiting, think about network calls executed in sync. in those cases the calling thread does nothing but wait for the server's response.
to remove this waiting issue we use async programming, AKA callbacks. so you start a network call and specify a callback which will be invoked once the result is ready. but the callback model has its own issues, the callback hell, as in below code.
fun someAPICalls(){
getToken(){ token ->
login(token) { userID ->
getUser(userID){ user ->
// Save user in DB
// This nesting can be even deeper
}
}
}
}
As you can see this code is not something that can be considered manageable. with kotlin's suspending functions, all this reduces to
fun someAPICalls() = viewModelScope.launch{
val token = getToken() // suspending function calls (Retrofit, Room)
val userId = login(token)
val user = getUser(userId)
}
As you can see, this is very close to how sequential code is written.
Even though there are other options(RX etc) available to solve the callbacks issue, they do introduce their own semantics which you need to learn. on the other hand writing coroutines code is not that different from its sequntial counterpart, you only have to learn a few basic constructs(Dispatchers, Builders etc), and this is something that makes coroutines the best choice in this scenario.
Apart from this there are some other scenarios where coroutines can be used effectively, one such use case is the practice of thread offloading used in UI frameworks such as Android. when you want to execute a long running CPU bound operation, you don't do it on UI thread, instead you offload the operation to a background thread. this can also be acomplished very cleanly using one of the couroutines builder such as lifecycleScope.launch(Dispatchers.Default) {}
Coroutine is an abstraction above thread, its something that requires a thread to execute in same way as a thread requires a CPU core to execute. because of this there is certain overhead introduced by the coroutine managment, so if you need to perform a long running CPU bound operation which may require use of multiple threads, you are better off using threads (Java ExecutorService etc.).
Upvotes: 6