Kotlin Coroutines vs. Threads Performance Benchmark
techyourchance.com
techyourchance.com
> Under the hood, [Kotlin] coroutines are runnables with an additional parameter representing the continuation, running on an executor. If you are not going to make any use of the continuation, you do not need the coroutines, you are just adding overhead over runnables and fixedthreadpools (which is what you measure).
Sure, but, 15 years in a software career...I think the best parts have always come from checking what things did in theory versus practice
Heresy! But my async!
Edited for clarification.
My opinion as an android dev: he's definitely very knowledgeable, but you should also hear other opinions. It's not a writer you should listen to blindly.
No writer should be trusted blindly but i don’t thjnk you meant that literally ;) just for others to see. all information should have some verification even from trusted sources.
on the author though, can you share a bit about the subject and the author’s participation in android dev? i’ve never touched android anything dev wise so i’m not even sure if the topic being discussed is a new and unexplored territory, explored but still evolving, or if it’s something the community has settled on with plenty of data to back their conclusion. similarly im not sure if the author is really skilled enough to have controversial opinions or if it’s more they’re alright but excitable.
how would you position the two points just from your point of view?
i get the impression the author of the article is controversial and also opinionated i. “unusual” ways — for me, such persons are interesting to hear from even if a lot of their ideas are non-sense or fanciful. “out-there” ideas often are just out there for no reason, but sometimes there’s gold in such ideas, or good thoughts and starting points for actual golden ones.
wiuod you personally at least listen to this author just in case there’s some gold or are they very much so just in their own world?
While I agree with his opinions about cargo culting in the Android development world, many of his opinions lack substance and can be distilled to: "X sucks! it's hard to learn! don't use X! ... unless you pass my course". It's the classic "controversy > audience > course" funnel most of these twitter famous developers have.
Hate to be this guy, but If you look at his code, you'll find that this person wouldn't pass an average interview for a mid-level developer. From basics like naming and function scoping to weird architecture choices and abstractions. His "best practices" includes MVC that doesn't make sense as MVC. That is who is teaching people and selling courses.
It's sad that a lot of new developers get hooked onto what these people are saying as an avoidance tactic - "I don't understand X properly yet, the twitter man says X is complicated, ergo X = bad". Then they decline to learn best practices which can negatively impact their career - i.e. someone is going to use this as a "don't use coroutines" excuse, leading to them not using and understanding coroutines. This will mean that person will not learn about async/await, structured concurrency, how it works under the hood.
How will this person integrate with a team that uses the modern best practices? Even if they manage to pass the interviews, they'll be a lot behind the rest of the team in understanding some great computational concepts.
Unfortunately, people like these don't seem to care that they are having negative impact on their audience, as long as they get to sell their course.
Yet another Twitter/Reddit personality selling sketchy courses to the clueless crowd?
The language affordances of coroutines are not to be ignored. But performance is a siren song.
But under the hood, Dispatchers schedule on Executors, usually ThreadPoolExecutor. So the more apt technology to compare to is VirtualThread with structured concurrency, and the takeaway should be that coroutines are an ergonomic way to manage concurrent work. Secondarily, they _can_ improve performance by reducing the number of OS threads, if the alternative were to naively spawn them and wait on I/O or something.
Coroutines by default are launched without an dispatcher, and simply create a single threaded event loop that schedules a single co-routine at a time to use the thread that created the coroutine scope. In this world the is no multithreading and coroutines can’t be moved between system threads, and no thread pools or equivalent are created.
However in most uses of coroutines (especially on Android), you’ll have a framework that already created a coroutine scope (using something like runBlocking) and provided the default dispatcher as the coroutine scopes dispatcher, which is then automatically inherited by any child coroutines or coroutines scope.
Personally I wished they had called Dispatchers.Default something else, it’s a confusing and slightly misleading name, given Dispatchers aren’t a hard requirement to use coroutines.
But I agree the big think to take away from Kotlin coroutines is structured concurrency, and the ergonomics to provides to manage concurrent operations in simple clean manner that avoids nasty resource leakages.
For everything else in coroutines, it’s pretty easy to reconfigure the coroutine runtime to use also most any underlying threading model, including ThreadPools, Singleton Threads, virtual threads, and my favourite, no additional threads whatsoever.
The above means that measuring coroutine startup and creation time and comparing it to threads kinda misses the point of using coroutines in the first place. And it’s possible to make coroutine creation cheaper or more expensive, depending on how exactly you’ve setup your coroutine scope, and which dispatchers (if any) you’re using.
And unlike virtual thread. The underlying infrastructure is exposed directly to end user. So you can write your own dispatcher or continuation with ease as long as you know what are you doing. Make it more useful as a low level building block of a high level framework/library.
The cost you pay for this gain in fine grained control (which you rarely need) is unfortunately the colored function problem, and in my experience it is rather annoying. Also the complexity of having a second unit-of-computation abstraction besides threads, and Coroutines aren’t a small API surface (goes along with Flows).
tl;dr: the inferior developer experience (added complexity, colored functions) must be offset by a gain in usefulness, and I just don’t see it
You can schedule coroutines over a threadpool. From the author's code, I'm not even sure this was accounted for in the benchmark. If they are using a threadpool and vanilla coroutines, without specifying where they are scheduled, yes, of course it won't start up a pool of threads for you and be as fast as a pool of threads. It would be terrible if it did that by default.
The comparison is nonsensical. It's not an either or kind of thing. If your co-routines are doing any blocking IO, you'd use the built in IO dispatcher, which is backed by a thread pool or you'd create your own dispatcher. Each co-routine gets its own thread if you use that; which can then do blocking IO and block the thread. Very common on the jvm if you are interacting with blocking IO things. I was working on some code that needed this yesterday.
The examples in this "benchmark" don't make any sense. It's not clear what the work is or why threads/co-routines are even needed. Handling UI events with threads is not a thing. But co-routines are very useful for that. Because they have very low overhead.
The rule of thumb should be:
- Is your task CPU bound? Put it in a thread pool.
- Is your task spending most of its time waiting (e.g. network call)? Use a coroutine