kotlin - 协程比 Kotlin 中的 Thread 快吗?为什么?如何获得“上下文切换”的时间?
问题描述
我正在测试 Thread 和 Coroutine 之间的速度。
我发现了一个有趣的东西。
当 Thread 和 Coroutine 的数量非常少时,Thread 速度更快。但是,当数字变大时,协程会快得多。
这是我测试出来的代码。
class ExampleUnitTest {
val reps = 1000000
val sumSize = 999
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
@Test
fun runInThread() {
var sum = 0
val threadList = ArrayList<Thread>()
println("[start] Active Thread = ${Thread.activeCount()}")
val time = measureTimeMillis {
repeat(reps) {
val mThread = Thread {
// println("start: ${Thread.currentThread().name}")
// Thread.sleep(1000L)
// println("end: ${Thread.currentThread().name}")
}
mThread.start()
threadList += mThread
}
println("[end] Active Thread= ${Thread.activeCount()}")
threadList.forEach {
it.join()
}
}
println("Time: $time ms\n")
}
@Test
fun runInCoroutine() {
var sum = 0
val jobList = ArrayList<Job>()
runBlocking {
println("[start] Active Thread = ${Thread.activeCount()}")
val time = measureTimeMillis {
repeat(reps) {
val job = launch(Dispatchers.Default) {
// println("start: ${Thread.currentThread().name}")
// delay(1000L)
// println("end: ${Thread.currentThread().name}")
}
jobList += job
}
println("[end] Active Thread= ${Thread.activeCount()}")
jobList.forEach {
it.join()
}
}
println("Time: $time ms\n")
}
}
}
尝试 | 代表大小 | 线程时间(毫秒) | 协程时间(ms) |
---|---|---|---|
1 | 10 | 1 | 63 |
2 | 100 | 8 | 65 |
3 | 1000 | 55 | 90 |
4 | 10000 | 426 | 175 |
5 | 100000 | 4089 | 395 |
6 | 1000000 | 43868 | 3165 |
最后,事实证明使用协程比使用大量线程更快。
但是,我认为仅“上下文切换”不会花费那么多时间,因为任务是空的,并且上下文切换工作看起来非常小。上下文切换会产生如此大的差异吗?
解决方案
我稍微简化了您的代码并添加了一个重复测量的循环:
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.concurrent.thread
import kotlin.system.measureTimeMillis
fun main() {
repeat(5) {
runInCoroutine(10)
}
}
fun runInCoroutine(reps: Int) {
runBlocking {
measureTimeMillis {
val jobList = ArrayList<Job>()
repeat(reps) {
jobList += launch { }
}
jobList.forEach { it.join() }
}.also {
println("Time: $it ms\n")
}
}
}
这是我的典型输出:
Time: 15 ms
Time: 1 ms
Time: 1 ms
Time: 0 ms
Time: 0 ms
正如您所看到的,第一次运行除了“第一次运行代码的时间”之外,并没有概括为其他任何事情。我还注意到我的第一次运行速度比你这边快四倍,我使用的是 Java 16 和 Kotlin 1.4.32。
编辑
我扩展了这个例子,更真实地展示了协程在“上下文切换”方面的优势。现在每个任务连续十次休眠 1 ms,我们使用 10,000 个任务:
import kotlinx.coroutines.*
import java.lang.Thread.sleep
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.NANOSECONDS
import kotlin.concurrent.thread
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis
fun main() {
val numTasks = 10_000
repeat(10) { _ ->
measureNanoTime {
runInCoroutine(numTasks)
}.also { tookNanos ->
println("Took %,d ms".format(NANOSECONDS.toMillis(tookNanos)))
}
}
}
fun runInCoroutine(numCoroutines: Int) {
List(numCoroutines) {
GlobalScope.launch {
repeat(10) { delay(1) }
}
}.also { jobs ->
runBlocking {
jobs.forEach { it.join() }
}
}
}
fun runInThread(numThreads: Int) {
List(numThreads) {
thread {
repeat(10) { sleep(1) }
}
}.forEach {
it.join()
}
}
对于runInCoroutine
,我得到以下信息:
Took 557 ms
Took 341 ms
Took 334 ms
Took 312 ms
Took 296 ms
Took 264 ms
Took 296 ms
Took 302 ms
Took 304 ms
Took 286 ms
而对于runInThread
,我明白了:
Took 729 ms
Took 682 ms
Took 654 ms
Took 658 ms
Took 662 ms
Took 660 ms
Took 686 ms
Took 706 ms
Took 689 ms
Took 697 ms
协程代码花费的时间减少了 2.5 倍。它可能还使用了更少的 RAM,但我没有测试那部分。
推荐阅读
- c# - GeckoFx 从呈现的 URL 打印
- css - VS Code 中的 CSS 设置更漂亮
- python - 使用 Pandas 附加 excel 电子表格
- pytorch - Pytorch - 为什么预分配内存会导致“尝试第二次向后遍历图形”
- java - oracle 中的日期保存不适用于 ISO 8601 标准
- angular - 是否有用于创建/处理 HTTP 流的 RXJS 标准?
- graphql - 为什么每个 GraphQL 查询/突变都有“两个名称”?
- bash - 为什么这个 bash/shell 脚本要从字符串中删除重复字符?
- r - 无法从未跟踪的更改中删除 .Rproj.user 文件夹
- django - 在 django 中间件中检索堆栈跟踪