Kotlin异步速度测试结果评估

Krz*_*dek 5 asynchronous kotlin kotlin-coroutines

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

  • 使用默认的启动类型(立即)进行异步 [ async ]
  • CompletableDeferred +启动(基本上是Job)[ cdl ]
  • CompletableDeferred + startCoroutine [ ccdl ]

结果显示在这里: 检测结果

简而言之,每个用例测试的每次迭代都会生成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却很少。我将以改变的测试顺序重新运行这些测试,但似乎与协程机制下的某些东西有关。

Mar*_*nik 0

由于launchasync是作为低级原语之上的一层构建的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.