协程比 Kotlin 中的 Thread 快吗?为什么?我怎样才能得到'context switching'的时间?
Is coroutine faster than Thread in Kotlin? And why? How can I get the time of 'context switching'?
我正在测试线程和协程之间的速度。
我发现了一个有趣的东西。
当Thread和Coroutine的数量很少时,Thread速度更快。
但是,当数字变大的时候,Coroutine就快多了。
这是我测试过的代码。
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")
}
}
}
try
reps size
Thread time(ms)
Coroutine time(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
最后,事实证明使用协程比使用大量线程更快。
但是,我不认为只有 'context switching' 需要那么多时间,因为任务是空的,上下文切换工作看起来非常小。上下文切换能带来那么大的不同吗?
默认情况下,任务 运行 带有线程,这通常足以满足您的日常程序。当有很多任务 运行 时,它们往往会变慢,这就是为什么使用协程有助于加快速度。线程 运行 串行任务,而协程 运行 同时执行许多任务。
编辑:我认为 this 应该对你有帮助。
我稍微简化了您的代码并添加了一个重复测量的循环:
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 毫秒,我们使用 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,但我没有测试那部分。
最佳答案大概是
存在多个差异 - 线程消耗更多 OS 资源,因为它们链接到 OS 线程。并发线程通常会切换,这很昂贵。协程不使用 OS 资源,并发协程之间的切换很便宜。
上面的答案不会令很多人满意。不满意者->
“Couroutine 导致一个模型,其中所有数据都是线程私有的,而在普通线程中,数据在线程之间共享。因此在执行时节省了大量处理器时间”。
我正在测试线程和协程之间的速度。
我发现了一个有趣的东西。
当Thread和Coroutine的数量很少时,Thread速度更快。 但是,当数字变大的时候,Coroutine就快多了。
这是我测试过的代码。
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")
}
}
}
try | reps size | Thread time(ms) | Coroutine time(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 |
最后,事实证明使用协程比使用大量线程更快。
但是,我不认为只有 'context switching' 需要那么多时间,因为任务是空的,上下文切换工作看起来非常小。上下文切换能带来那么大的不同吗?
默认情况下,任务 运行 带有线程,这通常足以满足您的日常程序。当有很多任务 运行 时,它们往往会变慢,这就是为什么使用协程有助于加快速度。线程 运行 串行任务,而协程 运行 同时执行许多任务。
编辑:我认为 this 应该对你有帮助。
我稍微简化了您的代码并添加了一个重复测量的循环:
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 毫秒,我们使用 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,但我没有测试那部分。
最佳答案大概是
存在多个差异 - 线程消耗更多 OS 资源,因为它们链接到 OS 线程。并发线程通常会切换,这很昂贵。协程不使用 OS 资源,并发协程之间的切换很便宜。
上面的答案不会令很多人满意。不满意者->
“Couroutine 导致一个模型,其中所有数据都是线程私有的,而在普通线程中,数据在线程之间共享。因此在执行时节省了大量处理器时间”。