Kotlin异步速度测试结果评估

时间:2019-11-28 22:19:40

标签: asynchronous kotlin kotlin-coroutines

我做了一些测试,比较了使用异步作为延迟结果的方法和CompletableDeferred与Job或startCoroutine的组合来完成相同工作的速度。 总之,有3个用例:

  • 具有默认启动类型的异步(立即)[异步]
  • CompletableDeferred +启动(基本上是Job)[ cdl ]
  • CompletableDeferred + startCoroutine [ ccdl ]

结果显示在这里: test results

简而言之,每个用例测试的每次迭代都会生成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()
        }
    }
}

毫不奇怪,前两个用例(异步和cdl)彼此非常接近,并且异步总是更好(因为您没有创建工作来完成延迟对象的开销),而是比较异步与CompletableDeferred + startCoroutine之间有一个巨大的差距(几乎2倍),而最后一个则受青睐。为什么会有如此大的差异?如果有人知道,为什么我们不应该只使用CompletableDeferred + startCoroutine包装器(如此处的completable())而不是异步使用?

加法1: 这是1000分的示例: enter image description here 异步和cdl结果中存在不断的尖峰,而ccdl中可能存在尖峰(也许是gc?),但ccdl却很少。我将以改变的测试顺序重新运行这些测试,但似乎与协程机制下的某些东西有关。

Edit1:

我已经接受了Marko Topolnik的回答,但是,如果您在启动范围内等待结果,您仍然可以使用这种“按他​​所说的”裸启动方法。

例如,如果您将启动几个有延迟的协程(异步),并且在该作用域的末尾,您将等待所有协程,那么ccdl方法将按预期工作(至少从我在测试中所见)。 / p>

1 个答案:

答案 0 :(得分:0)

由于launchasync被构建为低级基元createCoroutineUnintercepted()之上的一层,而startCoroutine实际上是对其的直接调用,因此基准测试结果没有任何意外。

  

为什么我们不应该仅使用CompletableDeferred + startCoroutine包装器(例如此处的completable())而不是async

代码中的注释已提示答案:

/*
 * find way to cancel startedCoroutine when deferred is
 * canceled (currently you have to cancel whole context)
 */

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())
                }
            })
        }

运行此命令时,您会看到10毫秒后取消了协程。 runBlocking生成器没有意识到它必须等待它完成。如果将bareLaunch {替换为launch {,则将恢复子协程正常完成的设计行为。 bareAsync也会发生同样的事情。