Kotlin协程Dispatchers性能疑问:IO与Default对比及异常结果解析
Kotlin协程调度器CPU密集型任务基准测试分析
我针对Kotlin协程的Dispatchers.Default和Dispatchers.IO调度器处理CPU密集型任务(计算阶乘)做了基准测试,同时对比了原生线程的表现。
初始测试代码
@State(Scope.Benchmark) class DispatchersCPUBenchMark { @Param("2000", "4000") var factorialSize = 0 var nCoroutines = 4000 @Benchmark fun coroutinesDispatchersIO() = run { repeat(nCoroutines) { GlobalScope.launch(Dispatchers.IO) { factorial(factorialSize) } } } @Benchmark fun coroutinesDispatchersDefault() = run { repeat(nCoroutines) { GlobalScope.launch(Dispatchers.Default) { factorial(factorialSize) } } } @Benchmark fun threads() { val threads = List(nCoroutines) { thread(start = true) { factorial(factorialSize) } } threads.forEach { it.join() } } val factorial = fun(n: Int): BigInteger? { var result = BigInteger.ONE for (i in 2..n) result = result.multiply(BigInteger.valueOf(i.toLong())) return result } }
初始测试结果
main summary: Benchmark (factorialSize) Mode Cnt Score Error Units DispatchersCPUBenchMark.coroutinesDispatchersDefault 2000 avgt 3 7074.923 ± 82728.394 us/op DispatchersCPUBenchMark.coroutinesDispatchersDefault 4000 avgt 3 5356.445 ± 52996.999 us/op DispatchersCPUBenchMark.coroutinesDispatchersIO 2000 avgt 3 5989.716 ± 22087.285 us/op DispatchersCPUBenchMark.coroutinesDispatchersIO 4000 avgt 3 7330.124 ± 37986.010 us/op DispatchersCPUBenchMark.threads 2000 avgt 3 1655875.859 ± 285008.082 us/op DispatchersCPUBenchMark.threads 4000 avgt 3 5904109.034 ± 312283.563 us/op
疑问解答
为什么
Dispatchers.Default下factorialSize=4000的耗时反而更短?
初始测试的核心问题是没有等待协程执行完成,仅测试了协程的启动开销。不管计算任务的大小,协程启动的逻辑是一致的,而结果中巨大的误差范围(比如Default2000的误差超过8万微秒)也说明数据可信度很低,这种反直觉的差异完全是误差或者JVM即时编译优化导致的偶然结果,不能反映实际任务的执行耗时。为什么
Dispatchers.IO的耗时比Dispatchers.Default更短?
同样因为初始测试没有等待协程完成,测的只是协程启动的开销。Dispatchers.IO和Default的协程启动开销本身差异极小,加上测试误差范围大,才出现了IO耗时更短的假象。这并不能说明IO调度器更适合CPU密集任务。
优化后测试结果
使用Blackhole保留计算结果(避免JVM优化掉无意义的计算)、runBlocking等待所有协程完成、async/await管理协程生命周期后,得到如下结果:
main summary: Benchmark (factorialSize) Mode Cnt Score Error Units DispatchersCPUBenchMark.coroutinesDispatchersDefault 2000 avgt 3 2526096.650 ± 111881.923 us/op DispatchersCPUBenchMark.coroutinesDispatchersDefault 4000 avgt 3 12238701.967 ± 2404781.746 us/op DispatchersCPUBenchMark.coroutinesDispatchersIO 2000 avgt 3 2450406.747 ± 248896.953 us/op DispatchersCPUBenchMark.coroutinesDispatchersIO 4000 avgt 3 12465975.900 ± 6017831.426 us/op DispatchersCPUBenchMark.threads 2000 avgt 3 1652601.100 ± 226224.137 us/op DispatchersCPUBenchMark.threads 4000 avgt 3 5877652.850 ± 689588.189 us/op
优化后结果分析
优化后的结果符合预期:任务规模越大,总耗时越长。对于Dispatchers.Default和IO的差异:
Default调度器的线程数默认等于CPU核心数,专门针对CPU密集任务优化,避免过多线程导致的上下文切换开销;IO调度器的线程池默认可扩容至64个线程,当运行大量CPU密集任务时,过多的线程会增加上下文切换的开销,理论上Default应该更高效。- 测试中
factorialSize=4000时IO仅比Default多0.2秒左右,且IO的误差范围极大(超过6秒),说明这个差异可能在误差范围内。结合CPU密集任务的特性,这个结果是符合预期的——两者的差异主要来自调度器的线程管理开销,而在纯CPU计算的任务中,这个开销占总耗时的比例很小,所以差异不明显。
内容的提问来源于stack exchange,提问作者Dzmitry Kashlach
相关产品推荐
相关产品推荐

