Luis
02/02/2024, 1:04 AMJeff Lockhart
02/02/2024, 1:08 AMJoffrey
02/02/2024, 8:26 AMrunBlocking
, and in this specific case the dispatcher is a single-threaded event loop, so no parallelism. But multi-threaded dispatchers are very common, for instance Dispatchers.Default
or <http://Dispatchers.IO|Dispatchers.IO>
.CLOVIS
02/02/2024, 9:01 AMval singleThread = newSingleThreadContext("single")
val singleThreadScope = CoroutineScope(singleThread)
repeat(3) { id ->
singleThreadScope.launch {
var counter = 10
while (isActive && counter > 0) {
println("Running in $id")
counter--
delay(1)
}
}
}
These are concurrent and parallel, because the dispatcher has multiple threads (as already pointed out above) (playground):
val multiThreadScope = CoroutineScope(Dispatchers.IO)
repeat(3) { id ->
multiThreadScope.launch {
var counter = 10
while (isActive && counter > 0) {
println("Running in $id")
counter--
delay(1)
}
}
}
Luis
02/02/2024, 3:52 PMsuspend fun main(): Unit = runBlocking{
val numberOfTimes = 1_000_000
val arraySize = 10
measureTimeMillis {
coroutineScope {
repeat(numberOfTimes){
launch {
Array(arraySize) { it.hashCode() }
}
}
}
}.apply {
println("With corroutines: $this ms")
}
measureTimeMillis {
repeat(numberOfTimes){
Array(arraySize) { it.hashCode() }
}
}.apply {
println("Without corroutines: $this ms")
}
}
CLOVIS
02/02/2024, 4:31 PMrunBlocking
, which uses a single thread. Try using withContext(Dispatchers.Default) { … }
instead of just coroutineScope { … }
to use a different dispatcher.
• Since both your examples use a single thread, you are comparing "running everything sequentially in a single for loop" (repeat
is compiled to a regular for
loop) to "running everything sequentially, but each iteration has to be submitted to the dispatcher's event queue, ran, and then the next one must be scheduled" which is much harder to optimize by the JVM.CLOVIS
02/02/2024, 4:34 PMJoffrey
02/02/2024, 4:35 PMwithContext(Dispatchers.Default)
CLOVIS
02/02/2024, 4:37 PMLuis
02/02/2024, 8:03 PMimport kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.system.measureTimeMillis
suspend fun main(): Unit = withContext(Dispatchers.Default){
val numberOfTimes = 10_000
val arraySize = 10
measureTimeMillis {
repeat(numberOfTimes){
Array(arraySize) { it.hashCode() }
}
}.apply {
println("Without corroutines: $this ms")
}
measureTimeMillis {
repeat(numberOfTimes){
launch {
Array(arraySize) { it.hashCode() }
}
}
}.apply {
println("With corroutines: $this ms")
}
}
And thanks in advance for the interactionsJeff Lockhart
02/02/2024, 10:49 PMmeasureTimeMillis {
coroutineScope {
repeat(numberOfTimes) {
launch {
doWork()
}
}
}
}
Also note, the level of parallelism is very much dependent on the system you're running on. I have no idea how many CPU cores the Kotlin Playground executes with.Luis
02/03/2024, 1:14 AMJeff Lockhart
02/03/2024, 2:09 AMLuis
02/03/2024, 3:55 PMJeff Lockhart
02/04/2024, 11:01 PMimport kotlinx.coroutines.*
import kotlin.time.measureTime
suspend fun main() {
val numberOfTimes = 16
val value = 40
measureTime {
repeat(numberOfTimes) {
val time = measureTime {
fibonacci(value)
}
println("$it $time")
}
}.apply {
println("Without coroutines: $this")
}
measureTime {
coroutineScope {
repeat(numberOfTimes) {
launch(Dispatchers.Default) {
val time = measureTime {
fibonacci(value)
}
println("$it $time")
}
}
}
}.apply {
println("With coroutines: $this")
}
}
fun fibonacci(n: Int): Long {
return if (n <= 1) {
n.toLong()
} else {
fibonacci(n - 1) + fibonacci(n - 2)
}
}
Running on my 16-core machine, I get this output:
0 249.901979ms
1 248.530566ms
2 254.228630ms
3 249.449091ms
4 239.983553ms
5 243.419348ms
6 243.652377ms
7 249.540971ms
8 252.376248ms
9 256.178971ms
10 252.962235ms
11 245.185620ms
12 250.231187ms
13 246.561204ms
14 251.857170ms
15 247.229061ms
Without coroutines: 4.012906210s
0 276.228992ms
3 277.375637ms
15 275.671535ms
12 277.222348ms
10 278.674351ms
1 280.761071ms
2 281.079150ms
7 281.659647ms
5 281.960807ms
11 281.052791ms
14 281.033821ms
9 282.077916ms
4 282.861293ms
8 283.675079ms
6 285.752790ms
13 286.216757ms
With coroutines: 348.880909ms
The slightly faster individual execution times when running sequentially is likely due to CPU single-threaded performance boosting.Jeff Lockhart
02/04/2024, 11:21 PM0 570.481784ms
1 495.259729ms
2 498.058526ms
3 493.501416ms
4 488.237780ms
5 486.254588ms
6 495.960375ms
7 502.377977ms
8 484.046018ms
9 503.903930ms
10 489.172675ms
11 503.375202ms
12 505.227134ms
13 490.989507ms
14 486.554507ms
15 491.134007ms
Without coroutines: 8.013552646s
0 14.861984062s
14 14.867873495s
9 14.915033116s
8 14.933522893s
11 14.955004188s
7 14.974335832s
2 14.985772431s
6 14.991468946s
10 15.022878786s
5 15.025456945s
3 15.026649970s
1 15.041704843s
4 15.044281802s
13 15.042278650s
12 15.047098948s
15 15.046905120s
With coroutines: 15.084903500s
Everything is taking longer, and the more coroutines running in parallel, the longer each of them takes to complete. So now I'm baffled about what's going on in my library's JVM test environment! 😂 I don't get the same results running the same code on native targets or from the library directly. Only JVM tests. 😕Luis
02/05/2024, 12:28 AM0 1.600314200s
1 1.493324800s
2 1.520053800s
3 1.514458400s
4 1.472961100s
5 1.479469700s
6 1.507933200s
7 1.469162900s
8 1.490816500s
9 1.489173900s
10 1.482828100s
11 1.495576700s
12 1.532814300s
13 1.523432700s
14 1.555138600s
15 1.513885900s
Without coroutines: 24.196978500s
3 1.805911900s
6 1.779178100s
7 1.785112900s
2 1.947047900s
0 1.965964900s
1 2.071362300s
4 2.068797800s
5 2.060026600s
9 1.677584200s
8 1.711236500s
10 1.694684300s
11 1.647069500s
12 1.754483200s
15 1.725787300s
14 1.749260900s
13 1.761648s
With coroutines: 4.006675500s
Luis
02/05/2024, 12:28 AMJeff Lockhart
02/05/2024, 12:30 AMJeff Lockhart
02/05/2024, 7:24 AMLuis
02/08/2024, 2:06 AMJeff Lockhart
02/08/2024, 4:37 AM