Krz*_*dek 5 asynchronous kotlin kotlin-coroutines
我进行了一些测试,比较了使用异步作为延迟结果的方法和CompletableDeferred与Job或startCoroutine的组合来完成相同工作的速度。总之,有3个用例:
简而言之,每个用例测试的每次迭代都会生成10000个async / cdl / ccdl请求,并等待它们完成。将其重复225次,并进行25次预热(不包括在结果中),并在上述过程的100次迭代中收集数据点(最小值,最大值,平均值)。
这是一个代码:
import com.source.log.log
import kotlinx.coroutines.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.startCoroutine
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis
/**
* @project Bricks
* @author SourceOne on 28.11.2019
*/
/*I know that there are better ways to benchmark speed
* but given the produced results this method is fine enough
* */
fun benchmark(warmUp: Int, repeat: Int, action: suspend () -> Unit): Pair<List<Long>, List<Long>> {
val warmUpResults = List(warmUp) {
measureNanoTime {
runBlocking {
action()
}
}
}
val benchmarkResults = List(repeat) {
measureNanoTime {
runBlocking {
action()
}
}
}
return warmUpResults to benchmarkResults
}
/* find way to cancel startedCoroutine when deferred is
* canceled (currently you have to cancel whole context)
* */
fun <T> CoroutineScope.completable(provider: suspend () -> T): Deferred<T> {
return CompletableDeferred<T>().also { completable ->
provider.startCoroutine(
Continuation(coroutineContext) { result ->
completable.completeWith(result)
}
)
}
}
suspend fun calculateAsyncStep() = coroutineScope {
val list = List(10000) {
async { "i'm a robot" }
}
awaitAll(*list.toTypedArray())
}
suspend fun calculateCDLStep() = coroutineScope {
val list = List(10000) {
CompletableDeferred<String>().also {
launch {
it.complete("i'm a robot")
}
}
}
awaitAll(*list.toTypedArray())
}
suspend fun calculateCCDLStep() = coroutineScope {
val list = List(10000) {
completable { "i'm a robot" }
}
awaitAll(*list.toTypedArray())
}
fun main() {
val labels = listOf("async", "cdl", "ccdl")
val collectedResults = listOf(
mutableListOf<Pair<List<Long>, List<Long>>>(),
mutableListOf(),
mutableListOf()
)
"stabilizing runs".log()
repeat(2) {
println("async $it")
benchmark(warmUp = 25, repeat = 200) {
calculateAsyncStep()
}
println("CDL $it")
benchmark(warmUp = 25, repeat = 200) {
calculateCDLStep()
}
println("CCDL $it")
benchmark(warmUp = 25, repeat = 200) {
calculateCCDLStep()
}
}
"\n#Benchmark start".log()
val benchmarkTime = measureTimeMillis {
repeat(100) {
println("async $it")
collectedResults[0] += benchmark(warmUp = 25, repeat = 200) {
calculateAsyncStep()
}
println("CDL $it")
collectedResults[1] += benchmark(warmUp = 25, repeat = 200) {
calculateCDLStep()
}
println("CCDL $it")
collectedResults[2] += benchmark(warmUp = 25, repeat = 200) {
calculateCCDLStep()
}
}
}
"\n#Benchmark completed in ${benchmarkTime}ms".log()
"#Benchmark results:".log()
val minMaxAvg = collectedResults.map { stageResults ->
stageResults.map { (_, benchmark) ->
arrayOf(
benchmark.minBy { it }!!, benchmark.maxBy { it }!!, benchmark.average().toLong()
)
}
}
minMaxAvg.forEachIndexed { index, list ->
"results for: ${labels[index]} [min, max, avg]".log()
list.forEach { results ->
"${results[0]}\t${results[1]}\t${results[2]}".log()
}
}
}
Run Code Online (Sandbox Code Playgroud)
毫不奇怪,前两个用例(异步和cdl)彼此非常接近,并且异步总是更好(因为您没有创建作业来完成延迟对象的开销),而是将异步与CompletableDeferred + startCoroutine进行比较他们之间有一个巨大的差距(几乎是2倍),而最后一个则是有利的。为什么会有如此大的差异,并且如果有人知道,为什么我们不应该只使用CompletableDeferred + startCoroutine包装器(如此completable()处)而不是异步呢?
加法一:这是1000分的示例:
异步和cdl结果中存在不断的尖峰,而ccdl中可能存在尖峰(也许是gc?),但ccdl却很少。我将以改变的测试顺序重新运行这些测试,但似乎与协程机制下的某些东西有关。
由于launch和async是作为低级原语之上的一层构建的createCoroutineUnintercepted(),而startCoroutine实际上是直接调用它,因此您的基准测试结果不会有任何意外。
为什么我们不应该只使用
CompletableDeferred+startCoroutine包装器(就像completable()这里)而不是async?
您的代码中的注释已经暗示了答案:
/*
* find way to cancel startedCoroutine when deferred is
* canceled (currently you have to cancel whole context)
*/
Run Code Online (Sandbox Code Playgroud)
您短路的层startCoroutine正是处理取消、协程层次结构、异常处理和传播等事务的层。
launch这是一个简单的示例,向您展示了当您替换为裸协程时会破坏的事情之一:
fun main() = runBlocking {
bareLaunch {
try {
delay(1000)
println("Coroutine done")
} catch (e: CancellationException) {
println("Coroutine cancelled, the exception is: $e")
}
}
delay(10)
}
fun CoroutineScope.bareLaunch(block: suspend () -> Unit) =
block.startCoroutine(Continuation(coroutineContext) { Unit })
fun <T> CoroutineScope.bareAsync(block: suspend () -> T) =
CompletableDeferred<T>().also { deferred ->
block.startCoroutine(Continuation(coroutineContext) { result ->
result.exceptionOrNull()?.also {
deferred.completeExceptionally(it)
} ?: run {
deferred.complete(result.getOrThrow())
}
})
}
Run Code Online (Sandbox Code Playgroud)
当您运行此命令时,您将看到裸协程在 10 毫秒后被取消。建造runBlocking者没有意识到必须等待它完成。如果替换bareLaunch {为launch {,您将恢复子协程正常完成的设计行为。同样的事情也会发生在bareAsync.
| 归档时间: |
|
| 查看次数: |
67 次 |
| 最近记录: |