【问题标题】:Which of coroutines (goroutines and kotlin coroutines) are faster? [closed]哪个协程(goroutines 和 kotlin coroutines)更快? [关闭]
【发布时间】:2018-04-02 12:59:45
【问题描述】:

Kotlin 协程是有限状态机和一些任务运行器的糖(例如,默认的 ForkJoinPool)。 https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#implementation-details

换句话说,在 java/kotlin 运行时中还没有运行时协程(但这可以通过 http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html 改变)。 Kotlin 协程只是一系列任务,一个一个地执行。每个任务都可以在线程池中的任意线程中执行。

Go 运行时支持“协程”。但是 goroutines 并不是真正的协程。 Goroutines 不允许在程序中设置屈服点。此外,Go 不允许设置自定义线程池。您可以只设置默认池中的线程大小。

kotlin 协程和 goroutines 的第一个区别是 Go 运行时管理此时正在运行的协程。当 goroutine 在某些 IO 操作(或同步原语)处被阻塞时,Go 会选择下一个 Job 来执行它。在 JVM 中,不存在这样的智能工作切换。

因此,Go 可以廉价地更改当前正在运行的作业。 Go 只需要更改几个注册表 https://groups.google.com/forum/#!msg/golang-nuts/j51G7ieoKh4/wxNaKkFEfvcJ。但是有人说,JVM 可以使用线程堆栈而不是寄存器。所以根本不需要保存和加载寄存器。

kotlin 协程和 goroutine 的第二个区别是协程的类型。 Kotlin 协程是无堆栈协程。 Goroutines 是堆栈式协程。 Kotlin 协程的所有状态都存储在 Kotlin 上下文中,该上下文存储在堆中。 Goroutines 状态存储在寄存器和线程堆栈中。

我想知道,哪些协程(goroutines 和 kotlin 协程)在 IO 绑定任务中更快? CPU绑定任务?内存消耗呢?

【问题讨论】:

  • “stackful coroutines”和“stackless coroutines”之间的区别是不明确且没有实际意义的。有关详细信息,请参阅我的 JVMLS 演讲:youtube.com/watch?v=3xalVUY69Ok

标签: go kotlin coroutine goroutine kotlin-coroutines


【解决方案1】:

Kotlin 中的协程的实现方式与 Go 中的 goroutine 不同,因此哪个“更快”取决于您要解决的问题和您编写的代码类型。

一般来说,很难提前判断哪一个会更好地解决您手头的问题。您必须针对您的特定工作负载运行基准测试才能弄清楚。不过,这里是对关键差异的一般总结,应该可以为您提供一些指导。

  • 与 Go 协程相比,Kotlin 协程每个简单实例所需的内存更少。 Kotlin 中一个简单的协程只占用几十字节的堆内存,而一个 Go 协程开始时有 4KiB 的堆栈空间。这意味着,如果你打算拥有数百万个协程,那么 Kotlin 中的协程可能会让你比 Go 更有优势。它还使 Kotlin 协程更适合于非常短暂的小型任务,例如生成器和惰性序列。

  • Kotlin 协程可以进入任何堆栈深度,但是每次调用挂起函数都会为其堆栈分配堆中的对象。 Kotlin 协程中的调用栈目前实现为堆对象的链表。相比之下,Go 中的 goroutines 使用线性堆栈空间。这使得 Go 中深栈的暂停更有效。因此,如果您正在编写的代码在堆栈的深处暂停,您可能会发现 goroutine 对您来说更高效。

  • 高效异步 IO 是一个非常多维的设计问题。对一种应用程序有效的方法可能无法为另一种应用程序提供最佳性能。 Kotlin 协程中的所有 IO 操作都是由用 Kotlin 或 Java 编写的库实现的。 Kotlin 代码有大量可用的 IO 库。在 Go 中,异步 IO 是由 Go 运行时使用通用 Go 代码不可用的原语实现的。如果实现 IO 操作的 Go 方法非常适合您的应用程序,那么您可能会发现它与 Go 运行时的紧密集成会给您带来优势。另一方面,在 Kotlin 中,您可以找到一个库或自己编写一个以最适合您的应用程序的方式实现异步 IO 的库。

  • Go 运行时完全控制在物理 OS 线程上调度 goroutines 的执行。这种方法的优点是您不必考虑所有问题。使用 Kotlin 协程,您可以对协程的执行环境进行细粒度控制。这很容易出错(例如,您可能只是创建了太多不同的线程池,并在它们之间的上下文切换上浪费了 CPU 时间)。但是,它使您能够为您的应用程序微调线程分配和上下文切换。例如,在 Kotlin 中,很容易在单个 OS 线程(或线程池)中执行整个应用程序或其代码子集,从而完全避免在 OS 线程之间切换上下文,只需为此编写适当的代码。

【讨论】:

  • «没有办法在代码中说“在同一个操作系统线程上运行这些 goroutines”。»不是 100% 正确:runtime.LockOSThread() 调用将调用 goroutine 锁定到它当前运行的 OS 线程。这将确保调用 goroutine 将始终被调度在同一个线程上,而其他 goroutine 不会。 OTOH,除了极少数情况外,这不是必需的,实际上通常会适得其反,因为 Go 调度程序会非常努力地确保从线程中删除的 goroutine 再次在其上调度,如果可能的话。
  • @Max,考虑阅读this classic essay,了解并发作为库和并发通过运行时调度程序之间的区别(此处:Java+anything vs Go)。
  • @kostix 谢谢。已更正,删除此部分以确保准确性。
  • @kostix 我实际上发现runtime.LockOSThread() 很有用。我有一个由 goroutine 并行化的 CPU 绑定任务。我实际上需要线程,但我们在 Go 中没有。我发现锁定 goroutine 要快得多。
猜你喜欢
  • 1970-01-01
  • 2014-07-06
  • 2021-03-29
  • 1970-01-01
  • 2021-08-23
  • 1970-01-01
  • 1970-01-01
  • 2011-05-23
  • 1970-01-01
相关资源
最近更新 更多