kotlin协程中的unrolling recursion via loop

3 阅读1分钟

kotlin协程中的unrolling recursion via loop

// BaseContinuationImpl.kt
public final override fun resumeWith(result: Result<Any?>) {
    // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
    var current = this
    var param = result
    while (true) {
        // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
        // can precisely track what part of suspended callstack was already resumed
        probeCoroutineResumed(current)
        with(current) {
            val completion = completion!! // fail fast when trying to resume continuation without completion
            val outcome: Result<Any?> =
                try {
                    val outcome = invokeSuspend(param)
                    if (outcome === COROUTINE_SUSPENDED) return
                    Result.success(outcome)
                } catch (exception: Throwable) {
                    Result.failure(exception)
                }
            releaseIntercepted() // this state machine instance is terminating
           
            // A1,这里为什么要这样?
            if (completion is BaseContinuationImpl) {
                // unrolling recursion via loop
                current = completion
                param = outcome
            } else {
                // top-level completion reached -- invoke and return
                completion.resumeWith(outcome)
                return
            }
        }
    }
}

作用

将递归调用转成循环避免深度递归导致的栈溢出或不必要的开销。

原理

构造一个状态机分布:

// C表示completion
// X,Y,Z为BaseContinuationImpl子类的状态机
// top completion为非BaseContinuationImpl子类

top completion
         <--C-- X
               <--C-- Y
                     <--C-- Z

假设没有这种优化,则当Z.resumeWith()被调用,依次恢复状态机执行,直到(top completion).resumeWith()被调用时,调用栈为:

Z.resumeWith() 
    --> Y.resumeWith() 
    --> X.resumeWith() 
    --> top completion.resumeWith() 

可以看到,随着状态机的增多,调用栈会越来越深,性能消耗越来越大!

启用优化,当Z.resumeWith()被调用时后续执行情况为:

// 调用栈:Z.resumeWith() --> Z.invokeSuspend()
1. 执行Z.invokeSuspend()

// 调用栈:Z.resumeWith() 
2. 进入“unrolling ”分支,局部变量completion赋值为Y
  
// 调用栈:Z.resumeWith() --> Y.invokeSuspend()
3. 执行Y.invokeSuspend()

// 调用栈:Z.resumeWith() 
4. 进入“unrolling ”分支,局部变量completion赋值为X 
  
// 调用栈:Z.resumeWith() --> X.invokeSuspend()
5. 执行X.invokeSuspend()

// 调用栈:Z.resumeWith() --> (top completion).reumeWith() 
6. 进入“top completion”分支,调用(top completion).reumeWith()

可以看到不管有多少状态机,相比没有优化调用栈大大减少。