为什么Kotlin同逻辑的pmap扩展函数比手动实现代码运行更慢?
问题核心原因
- 首先是协程调度器冷启动开销:你的测试代码优先执行
pmap测试,此时Kotlin协程的默认调度器Dispatchers.Default、后台线程池还未完成初始化,第一次启动协程会产生额外的初始化开销,后续执行的manualpmap测试已经复用了初始化完成的调度器,所以耗时更低。你可以调换两个测试的执行顺序,会发现manualpmap的耗时反而会高于pmap。 - 其次是
pmap实现本身存在不合理设计:你在遍历Deferred列表时,为每个await()单独创建了runBlocking上下文,每次创建runBlocking都要初始化独立的事件循环、协程上下文,会产生不必要的额外开销。 - 最后是参数传递的额外间接调用:你调用
pmap时传入的lambda是{ transform(it) },相比manualpmap里直接在async块中调用transform,多了一层函数转发的开销,虽然这个开销占比很低,但也是差异来源之一。
优化方案
你可以按如下方式修改pmap实现,性能会和手动实现完全一致:
fun <T, U> List<T>.pmap( scope: CoroutineScope = GlobalScope, transform: suspend (T) -> U ): List<U> = runBlocking { // 一次性启动所有异步任务,然后统一等待所有任务完成,仅用一次runBlocking map { scope.async { transform(it) } }.awaitAll() }
如果要进一步优化,可以给函数加上inline修饰,同时给transform参数加上crossinline关键字,消除lambda调用的额外开销:
inline fun <T, U> List<T>.pmap( scope: CoroutineScope = GlobalScope, crossinline transform: suspend (T) -> U ): List<U> = runBlocking { map { scope.async { transform(it) } }.awaitAll() }
额外建议
- 尽量避免默认使用
GlobalScope,它不支持结构化并发,任务抛出异常或者被取消时无法自动联动,推荐由调用方传入自己的CoroutineScope。 - 性能测试时要注意排除冷启动影响,可以先预热执行几次测试逻辑,再统计正式的耗时数据,避免单次运行的初始化开销干扰结果。
内容的提问来源于stack exchange,提问作者Fedelway
相关产品推荐
相关产品推荐

