Kotlin 挂起函数递归调用

2023-12-04

突然发现递归调用挂起函数比调用相同的函数但没有调用要花更多的时间suspend修饰符,因此请考虑下面的代码片段(基本斐波那契数列计算):

suspend fun asyncFibonacci(n: Int): Long = when {
    n <= -2 -> asyncFibonacci(n + 2) - asyncFibonacci(n + 1)
    n == -1 -> 1
    n == 0 -> 0
    n == 1 -> 1
    n >= 2 -> asyncFibonacci(n - 1) + asyncFibonacci(n - 2)
    else -> throw IllegalArgumentException()
}

如果我调用此函数并使用以下代码测量其执行时间:

fun main(args: Array<String>) {
    val totalElapsedTime = measureTimeMillis {
        val nFibonacci = 40

        val deferredFirstResult: Deferred<Long> = async {
            asyncProfile("fibonacci") { asyncFibonacci(nFibonacci) } as Long
        }
        val deferredSecondResult: Deferred<Long> = async {
            asyncProfile("fibonacci") { asyncFibonacci(nFibonacci) } as Long
        }

        val firstResult: Long = runBlocking { deferredFirstResult.await() }
        val secondResult: Long = runBlocking { deferredSecondResult.await() }
        val superSum = secondResult + firstResult
        println("${thread()} - Sum of two $nFibonacci'th fibonacci numbers: $superSum")
    }
    println("${thread()} - Total elapsed time: $totalElapsedTime millis")
}

我观察到进一步的结果:

commonPool-worker-2:fibonacci - Start calculation...
commonPool-worker-1:fibonacci - Start calculation...
commonPool-worker-2:fibonacci - Finish calculation...
commonPool-worker-2:fibonacci - Elapsed time: 7704 millis
commonPool-worker-1:fibonacci - Finish calculation...
commonPool-worker-1:fibonacci - Elapsed time: 7741 millis
main - Sum of two 40'th fibonacci numbers: 204668310
main - Total elapsed time: 7816 millis

但如果我删除suspend修饰符来自asyncFibonacci函数,我会得到这个结果:

commonPool-worker-2:fibonacci - Start calculation...
commonPool-worker-1:fibonacci - Start calculation...
commonPool-worker-1:fibonacci - Finish calculation...
commonPool-worker-1:fibonacci - Elapsed time: 1179 millis
commonPool-worker-2:fibonacci - Finish calculation...
commonPool-worker-2:fibonacci - Elapsed time: 1201 millis
main - Sum of two 40'th fibonacci numbers: 204668310
main - Total elapsed time: 1250 millis

我知道最好重写这样的函数tailrec它将增加其执行时间 apx。几乎 100 次,但无论如何,这是什么suspend关键字这是否会将执行速度从 1 秒降低到 8 秒?

用 来标记递归函数是不是完全愚蠢的想法suspend?


作为介绍性评论,您的测试代码设置太复杂。这个更简单的代码在压力方面实现了相同的效果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 倍。这意味着所示的代码解释了可挂起性开销的很大一部分。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kotlin 挂起函数递归调用 的相关文章

  • 如何使用递归查找数字中的最小元素 [C]

    好的 所以我正在准备我的 C 考试 当谈到递归时我有点卡住了我是大学一年级的学生 这对我来说似乎有点困难 练习要求在给定的数字中使用递归函数我需要找到最小的元素 例如 52873 是 2 程序需要打印 2 include
  • 函数速度测试的奇怪结果

    我编写了一个使用递归来查找最大公因数 分母 的函数 gt gcd function a b if length a length b gt 1 warning Only scalars allowed using first element
  • 如何强制客户端代码使用合约初始化 Kotlin 中所有必需的构建器字段?

    在 2019 年 JetBrains 开放日上 据说 Kotlin 团队研究了合约并试图实现context允许仅在某些上下文中调用函数的合约 例如函数build仅当以下情况时才允许被调用setName方法在它之前被调用过一次 Here ht
  • 通过递归扩展 Prolog 目标?

    我 最终 实现了一些目标 这些目标将根据开始由 开始之后 and duration 然而 计划目标仅接受规定数量的任务 我想扩展计划目标的功能以接受单个列表并在计划时迭代该列表 不幸的是 我认为这将需要与can run and 冲突目标如下
  • 根据另一个列表的顺序对列表进行排序[重复]

    这个问题在这里已经有答案了 我需要对列表进行排序Person对象 List
  • Jackson Kotlin - 反序列化 JsonNode

    Problem 我有字符串形式的 JSON 内容 我首先想用 Jackson 以编程方式遍历它 然后 当我有感兴趣的节点时 我想反序列化它 我尝试过的 我已使用 mapper readValue 成功反序列化字符串 但现在我想在 jsonN
  • Firebird 和 Android JDBC 驱动程序

    火鸟有问题 我从未与 DB 合作过 服务器 firebird 1 5 上的数据库 添加库 firebird full 2 2 4到 libs 文件夹 将其添加到 Gradle implementation fileTree libs 将其添
  • Web 应用程序架构 - 需要作业/任务队列吗?

    我目前正在设计一个 Web 应用程序 该应用程序将允许用户安排将针对 HTTP API 代表他们 执行的任务 这些任务可以重复出现 并且可用于调度的最小时间分辨率为一分钟 由于任务的性质 我认为异步执行它们是有意义的 但是 这部分的架构应该
  • Python - 对象 MagicMock 不能在“await”表达式中使用

    当我尝试使用 MagicMock 在单元测试中模拟异步函数时 出现以下异常 类型错误 对象 MagicMock 不能在 await 表达式中使用 示例代码如下 source code class Service async def comp
  • 如何让BackgroundWorker返回一个对象

    我需要做RunWorkerAsync 返回一个List
  • 在此异步设置中,我在哪里捕获 KeyboardInterrupt 异常

    我正在开发一个使用ccxt异步库 它要求通过显式调用该类的资源来释放某个类使用的所有资源 close 协程 我想退出程序ctrl c并等待异常中的关闭协程 然而 它永远不会被等待 该应用程序由模块组成harvesters strategie
  • 部署到 AppEngine 时未调用 Ktor 应用程序的 Main 方法

    Issue Ktor 应用程序的main部署到 App Engine 时不会调用方法 在应用程序的主要方法中 逻辑是根据 API 请求检索内容Timer并将该信息保存到客户端使用的 Firestore 数据库中 目前 此逻辑在部署在Jar到
  • 为什么我应该使用 std::async?

    我试图深入探索新 C 11 标准的所有选项 在使用 std async 并阅读其定义时 我注意到两件事 至少在使用 gcc 4 8 1 的 linux 下 它被称为async 但它有一个真正的 顺序行为 基本上在你调用的行中future与您
  • 在 mongodb 中更新异步重复数据的最佳实践

    我正在权衡 Web 应用程序从关系 SQL 数据库迁移到 mongodb 的利弊 这是为了解决性能问题 将所有对象依赖项存储在对象本身中允许快速 读 用于向用户显示数据 另一方面 某些数据存在于不同的集合中 例如用户名位于用户集合中 但也在
  • 异步编程设计模式

    我正在为 CF NET 开发一个小型技术框架 我的问题是 我应该如何编写异步部分的代码 在 MSDN 上阅读了很多内容 但我不太清楚 所以 这是代码 public class A public IAsyncResult BeginExecu
  • Python脚本递归重命名文件夹和子文件夹中的所有文件

    您好 我有许多不同的文件需要重命名为其他文件 我已经走到这一步了 但我想要拥有它 这样我就可以有许多要替换的项目及其相应的替换项 而不是逐一输入 运行代码然后再次重新输入 更新 另外 我需要重命名以仅更改文件的一部分而不是整个文件 因此如果
  • 如何确保循环完成后执行语句?

    下面是我的代码的快照 routes index js exports index function req res var results new Array for var i 0 i lt 1000 i do database quer
  • python解释器自动重启而不返回答案

    调用递归函数时 python解释器会自动重新启动吗 我正在编写一个快速排序算法 并尝试对一个大的数字数组 顺序 10 4 进行排序 但是当我尝试对整个数组进行排序时 python 正在重新启动 即给我 重新启动 并且存储在内存中的所有值 函
  • 在 Kotlin 中声明静态属性?

    My Java code public class Common public static ModelPengguna currentModelPengguna public class Common companion object v
  • 如何在 Kotlin 中使用参数进行延迟初始化

    在 Kotlin 中 我可以在没有参数的情况下执行延迟初始化 如下声明 val presenter by lazy initializePresenter abstract fun initializePresenter T 但是 如果我的

随机推荐

  • Azure AD - SAML 单点注销 - 不支持的绑定 HTTP-POST

    我正在将 SAML 服务提供商与 MS AAD 集成 并且发现单点注销存在问题 我的服务提供商仅支持注销绑定 HTTP POST 而且AAD似乎只支持注销绑定 HTTP Redirect 根据我从 AAD 获得的 SAML 元数据 我认为是
  • 带和不带边界检查的 `std::bitset`

    再次关于STLstd bitset 它的文档说功能set reset test进行边界检查 并且operator 没有 我的计时实验表明功能set test通常比执行速度快 2 3 operator 我正在使用的代码是 typedef un
  • 将参数传递给 Swift 中的选择器函数[重复]

    这个问题在这里已经有答案了 我想通过一个范围到一个名为 a 的函数selector from a timer 具体参考一个cell这样我就可以更新一些内容UI 所以我想要这样的东西 timer Timer init timeInterval
  • 文档中键不同的多个字段平均聚合

    我得到的数据集如下 id ObjectId 592d4f43d69b643ac0cb9148 timestamp ISODate 2017 03 01T16 58 00 000Z Technique Meteo Direction moye
  • 将 IFrame 的 src 属性设置为 data:application/pdf;base64?

    将 IFrame 的 src 属性设置为 data application pdf base64 对我不起作用 有什么想法吗 这是 aspx 标记
  • Java - 设置首选项后备存储目录

    我需要在我的 Java 应用程序中创建一个持久存储 以便所有用户都可以访问它 所以我正在研究java util prefs Preferences并使用systemRoot 我在 Windows 上工作得很好 将数据保存在寄存器中 但我在
  • R 删除数据帧的第一行,直到第一行没有 NA

    我正在数据框上应用 na approx 如果 NA 恰好位于数据库的第一行或最后一行 则该方法将不起作用 如何编写执行以下操作的函数 当数据帧第一行的任何值为 NA 时 删除第一行 数据框示例 x1 x2 c 1 2 3 4 5 6 7 8
  • 如何为谷歌图表的x轴赋予样式?

    我正在实现 google 图表 我想为 x 轴数据提供样式 我发现下面的代码可以做到这一点 hAxis title Month textStyle fontSize 10 我想在文本数据下划线并将光标样式更改为指针 我在谷歌图表网站上搜索但
  • getter 和 setter 的设计是否糟糕?看到矛盾的建议[重复]

    这个问题在这里已经有答案了 我目前正在用 Java 开发一个具有几种不同模式的简单游戏 我扩展了一个主 Game 类 将主要逻辑放入其他类中 尽管如此 主要的游戏类别仍然相当庞大 快速浏览一下我的代码后 与游戏逻辑真正需要的其余部分相比 其
  • 是否有适用于 JVM 的 Web 框架可以在编译时检查数据绑定?

    通常 当您将某些属性绑定到 www 页面中的某些元素时 您在测试时就会知道拼写错误 我正在寻找 Web 框架 它在编译时会给我一个错误 我在绑定中犯了错误 找不到属性 或类似的东西 并假设我的 IDE 具有有效的重构机制 重命名属性也会影响
  • 如何在角度js中的按钮单击上使用过滤器

    您能告诉我如何在单击按钮时按升序或降序对数组进行排序吗 实际上我在标题 V 上有一个按钮 使用按钮单击我想按升序显示数据 实际上我正在 Ionic 中制作网格视图使用角度js 但我想使用按钮单击对其进行排序 我需要根据第一列对表进行排序 因
  • MySQL 是否比 PostgreSQL(在 Perl/DBI 下)更能抵抗 SQL 注入攻击?

    我正在审查一个基于 Linux 的 Perl Web 应用程序 其中包含一个具有普遍存在的登录处理程序 my sth DB gt prepare 从 userid userid 的密码中选择密码 或死 sth gt 执行或死亡 其中 use
  • 哪些操作系统支持 Java 中的本机(类似 inotify)文件监视

    JavaDoc 用于java nio file WatchService states 实施 是 旨在直接映射到本机文件事件通知 设施在可用的情况下 或使用原始机制 例如 当本机设施不可用时进行轮询 我认为这意味着它将在可能的情况下尝试一种
  • PostgreSQL NodeJS 中无法识别的配置参数“autocommit”

    message 翻译创建时出错 对于关键等等等等 名称 SequelizeDatabaseError stack SequelizeDatabaseError 无法识别的配置参数 自动提交 n Query module exports Qu
  • 使用 Intellij 和 JUnit 从控制台读取 System.in

    当通过 Idea 中的 main 运行代码时 以下代码运行良好 System in read 然而 junit 方法中的相同代码不起作用 public void testConsoleRead System in read 知道如何使这项工
  • 如何使 jQuery UI 可通过嵌套下拉菜单进行排序?

    我已阅读与此处发布的问题类似的所有内容 但没有找到任何解决方案 我创建了一个菜单 在下拉菜单中包含子菜单条目 所有菜单条目都可分类到所有菜单级别 根菜单条目到子列表 反之亦然 几乎一切都工作正常 但排序到第一个下拉列表会导致错误 不可能在第
  • 在同一列上使用多个 WHERE 条件进行 SELECTING

    好吧 我想我可能在这里忽略了一些明显 简单的东西 但是我需要编写一个查询 仅返回与同一列上的多个条件匹配的记录 我的表是一个非常简单的链接设置 用于将标志应用于用户 ID contactid flag flag type 118 99 Vo
  • 正则表达式可以在在线正则表达式测试器中工作,但不能在.NET中工作

    下列demo在线工作正常 但当我尝试在 c NET 中运行它时却不行 var regex new RegularExpressionAttribute Assert IsTrue regex IsValid email protected
  • 如何保存内部存储器中录制的音频

    我正在开发的android应用程序 它有录音选项 我希望它将新的音频录制文件保存在设备的内部存储中 以便用户和其他应用程序都无法访问这些录制的音频文件 除非他们打开我的应用程序 我的主要困难是能够将该音频文件保存在内部存储中 我花时间回顾了
  • Kotlin 挂起函数递归调用

    突然发现递归调用挂起函数比调用相同的函数但没有调用要花更多的时间suspend修饰符 因此请考虑下面的代码片段 基本斐波那契数列计算 suspend fun asyncFibonacci n Int Long when n lt 2 gt