作为介绍性评论,您的测试代码设置太复杂。这个更简单的代码在压力方面实现了相同的效果suspend fun
递归:
fun main(args: Array<String>) {
launch(Unconfined) {
val nFibonacci = 37
var sum = 0L
(1..1_000).forEach {
val took = measureTimeMillis {
sum += suspendFibonacci(nFibonacci)
}
println("Sum is $sum, took $took ms")
}
}
}
suspend fun suspendFibonacci(n: Int): Long {
return when {
n >= 2 -> suspendFibonacci(n - 1) + suspendFibonacci(n - 2)
n == 0 -> 0
n == 1 -> 1
else -> throw IllegalArgumentException()
}
}
我试图通过编写一个简单的函数来重现它的性能,该函数近似于suspend
函数必须执行以下操作才能实现可挂起性:
val COROUTINE_SUSPENDED = Any()
fun fakeSuspendFibonacci(n: Int, inCont: Continuation<Unit>): Any? {
val cont = if (inCont is MyCont && inCont.label and Integer.MIN_VALUE != 0) {
inCont.label -= Integer.MIN_VALUE
inCont
} else MyCont(inCont)
val suspended = COROUTINE_SUSPENDED
loop@ while (true) {
when (cont.label) {
0 -> {
when {
n >= 2 -> {
cont.n = n
cont.label = 1
val f1 = fakeSuspendFibonacci(n - 1, cont)!!
if (f1 === suspended) {
return f1
}
cont.data = f1
continue@loop
}
n == 1 || n == 0 -> return n.toLong()
else -> throw IllegalArgumentException("Negative input not allowed")
}
}
1 -> {
cont.label = 2
cont.f1 = cont.data as Long
val f2 = fakeSuspendFibonacci(cont.n - 2, cont)!!
if (f2 === suspended) {
return f2
}
cont.data = f2
continue@loop
}
2 -> {
val f2 = cont.data as Long
return cont.f1 + f2
}
else -> throw AssertionError("Invalid continuation label ${cont.label}")
}
}
}
class MyCont(val completion: Continuation<Unit>) : Continuation<Unit> {
var label = 0
var data: Any? = null
var n: Int = 0
var f1: Long = 0
override val context: CoroutineContext get() = TODO("not implemented")
override fun resumeWithException(exception: Throwable) = TODO("not implemented")
override fun resume(value: Unit) = TODO("not implemented")
}
你必须调用这个
sum += fakeSuspendFibonacci(nFibonacci, InitialCont()) as Long
where InitialCont
is
class InitialCont : Continuation<Unit> {
override val context: CoroutineContext get() = TODO("not implemented")
override fun resumeWithException(exception: Throwable) = TODO("not implemented")
override fun resume(value: Unit) = TODO("not implemented")
}
基本上,要编译一个suspend fun
编译器必须将其主体转变为状态机。每次调用还必须创建一个对象来保存机器的状态。当您恢复时,状态对象会告诉您要转到哪个状态处理程序。以上还不是全部,真正的代码更加复杂。
在解释模式下(java -Xint
),我得到了与实际几乎相同的性能suspend fun
,并且比启用 JIT 的真实速度快不到两倍。相比之下,“直接”函数实现的速度大约快 10 倍。这意味着所示的代码解释了可挂起性开销的很大一部分。