何时使用 Kotlin 协程来优化代码性能
When to use Kotlin Coroutines to optimize code performance
目前,我正在对 kotlin 协同程序进行一些试验,我问自己一个问题:使用协同程序是否有显着的性能提升?让我们看一些(理论上的)例子:
例子一
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()
}
}
}
还有更多示例,也许你们中的一个可以给出一些建议使用协程的示例。所以我最后的问题是(包括上面的问题):什么时候应该考虑用协程优化代码性能,什么时候开销大于获得的性能?
协程主要是一种用于需要大量等待的计算工具,想想同步执行的网络调用。在这些情况下,调用线程除了等待服务器的响应外什么都不做。
为了消除这个等待问题,我们使用异步编程,也就是回调。所以你开始一个网络调用并指定一个回调,一旦结果准备好就会调用它。但是回调模型有其自身的问题,the callback hell,如下面的代码所示。
fun someAPICalls(){
getToken(){ token ->
login(token) { userID ->
getUser(userID){ user ->
// Save user in DB
// This nesting can be even deeper
}
}
}
}
如您所见,这段代码不是可管理的。使用 kotlin 的暂停功能,所有这些都减少到
fun someAPICalls() = viewModelScope.launch{
val token = getToken() // suspending function calls (Retrofit, Room)
val userId = login(token)
val user = getUser(userId)
}
如您所见,这与顺序代码的编写方式非常接近。
尽管还有其他选项(RX etc) available to solve the callbacks issue, they do introduce their own semantics 你需要学习。另一方面,编写协程代码与其顺序代码没有什么不同,你只需要学习一些基本结构(Dispatchers , Builders 等),这使得协程成为这种情况下的最佳选择。
除此之外,还有其他一些可以有效使用协程的场景,其中一个用例是 UI 框架(例如 Android)中使用的线程卸载实践。当你想执行长 运行 CPU 绑定操作时,你不会在 UI 线程上执行它,而是将操作卸载到后台线程。这也可以使用协程构建器之一非常干净地完成,例如 lifecycleScope.launch(Dispatchers.Default) {}
哪些地方应该避免协程?
协程是线程之上的抽象,它需要一个线程来执行,就像线程需要一个 CPU 核心来执行一样。因此,协程管理引入了一定的开销,因此,如果您需要执行可能需要使用多个线程的长 运行 CPU 绑定操作,您最好使用线程(Java ExecutorService 等)。
目前,我正在对 kotlin 协同程序进行一些试验,我问自己一个问题:使用协同程序是否有显着的性能提升?让我们看一些(理论上的)例子:
例子一
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()
}
}
}
还有更多示例,也许你们中的一个可以给出一些建议使用协程的示例。所以我最后的问题是(包括上面的问题):什么时候应该考虑用协程优化代码性能,什么时候开销大于获得的性能?
协程主要是一种用于需要大量等待的计算工具,想想同步执行的网络调用。在这些情况下,调用线程除了等待服务器的响应外什么都不做。
为了消除这个等待问题,我们使用异步编程,也就是回调。所以你开始一个网络调用并指定一个回调,一旦结果准备好就会调用它。但是回调模型有其自身的问题,the callback hell,如下面的代码所示。
fun someAPICalls(){
getToken(){ token ->
login(token) { userID ->
getUser(userID){ user ->
// Save user in DB
// This nesting can be even deeper
}
}
}
}
如您所见,这段代码不是可管理的。使用 kotlin 的暂停功能,所有这些都减少到
fun someAPICalls() = viewModelScope.launch{
val token = getToken() // suspending function calls (Retrofit, Room)
val userId = login(token)
val user = getUser(userId)
}
如您所见,这与顺序代码的编写方式非常接近。
尽管还有其他选项(RX etc) available to solve the callbacks issue, they do introduce their own semantics 你需要学习。另一方面,编写协程代码与其顺序代码没有什么不同,你只需要学习一些基本结构(Dispatchers , Builders 等),这使得协程成为这种情况下的最佳选择。
除此之外,还有其他一些可以有效使用协程的场景,其中一个用例是 UI 框架(例如 Android)中使用的线程卸载实践。当你想执行长 运行 CPU 绑定操作时,你不会在 UI 线程上执行它,而是将操作卸载到后台线程。这也可以使用协程构建器之一非常干净地完成,例如 lifecycleScope.launch(Dispatchers.Default) {}
哪些地方应该避免协程?
协程是线程之上的抽象,它需要一个线程来执行,就像线程需要一个 CPU 核心来执行一样。因此,协程管理引入了一定的开销,因此,如果您需要执行可能需要使用多个线程的长 运行 CPU 绑定操作,您最好使用线程(Java ExecutorService 等)。