首页 > 解决方案 > 协程比 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

最后,事实证明使用协程比使用大量线程更快。

但是,我认为仅“上下文切换”不会花费那么多时间,因为任务是空的,并且上下文切换工作看起来非常小。上下文切换会产生如此大的差异吗?

标签: kotlinkotlin-coroutinescoroutine

解决方案


我稍微简化了您的代码并添加了一个重复测量的循环:

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,但我没有测试那部分。


推荐阅读