For my side projects, multithreading is part of the fun. Being able to efficiently break up and task out processes is just satisfying in a way that knowingly writing less efficient algorithms wouldn't be.
I use multithreading when appropriate, like saving or retrieving data in the background or event management, or processing tasks that can be done more efficiently in parallel, but otherwise I don't usually use it, no. I definitely default to not using it until I'd get an obvious benefit out of it.
The big one of those three when it's just my own projects is retrieving data. I tend not to work on complicated enough projects on my own that would require the other two all that often (and my side projects are usually games with relatively simple graphics).
A financial exchange does order matching at rates of millions of transactions per second on a single thread because its literally the fastest way to do things.
Would you consider operating the most important part of any financial exchange on a single thread "not interesting"?
The point I am ultimately trying to make is that, in many cases, the reason to avoid multi-threading is because it's not actually the fastest way to do something. It's that simple.
The implicit concern (if you could not infer from the content of my link above) being that the developer community is completely blind to the performance capabilities of a single computer/core/thread in 2022 and are inclined to automatically skip over these simple solutions in favor of a multi-threaded solution.
I feel like that is a point that doesn't need making because it's sorta elementary that task switching is expensive and avoidable in some cases. Especially when the explicit context is indie development, not squeezing some performance out of the threading model at large scale.
The essential practice (frequently ignored) is that you have to break the task into subtasks that take more time than the time to split between tasks. You usually can get away with a wide range (orders of magnitude) of subtask sizes that get a good speedup so you don't have to tune it exactly.
Whenever I try to get a parallel speedup I build some kind of batching before I do the actual subtask execution because almost always the smallest level of granularity is too small so instead of getting a 4x speedup on four cores you get a 20x slowdown. Whenever I do this in a "hackathon" environment with others I end up feeling like a Cassandra because the other people think that batching is optional or an optimization but unless the natural granularity of your task is huge it's essential if your goal is getting speedup.
Quite the opposite. I enjoy much greater liberty to design a performant app when I am doing it independently and I already know the space from experience. Sometimes I get to work like that professionally.
In Java I'd say that it is often easy and fun to use multithreading. Many tasks are "embarrassingly parallel", for instance raytracing. It is easy to split up this kind of job into smaller tasks and feed them into a ThreadPoolExecutor.
(Most people who try this, however, make it "not fun" and "not easy" by ignoring one essential fact which is that if you really want to get a speed-up you need to break the tasks into something that takes more time than it takes to switch a task! In the case of raytracing for instance, it probably takes more time to trace a single ray than it takes to switch tasks. If you bundled anywhere between 100 to 10,000 rays into a single task you'd probably get a good speedup. Often people have a fetish for some particular way to execute tasks in parallel and think that certain details of their approach are essential whereas the batching is essential to getting a speed-up if not to getting a correct answer.)
I work in Python a lot too, usually in Windows, and I am not in such a hurry to parallelize because the thread support in the language isn't natively strong (the GIL) and the more popular and mature multiprocessing libraries are frequently Unix only or only supported on Unix.
The problem is that multithreading is only rarely the best programming model. There's usually a different model that's easier to reason about which makes the behaviour you want easier to achieve. The implementation of that model might use threading under the hood, but abstracting that away is often the best thing to do.
It sounds like you've got a specific example in mind, though.
Depends which language I'm using. This is probably due to my own failings rather than any failing of the language, but I've ran into race conditions with Python that didn't happen with C# when doing very similar tasks.
The problem you run into with most games is that they have to have a consistent view of the game board at least once per "tick". Most games don't have issues calculating the state of the game in a single thread for most of a usual player's playthrough. The time where this fails is in simulation games when advanced players try to do hyper-scale things.
If I were limited on time and resources, I would focus on improving things that affect the 99% of standard players rather than go and add multithreading and deal with ACID issues.
Seems like a poorly-disguised moan about indie games often being poorly optimised?
Multithreading isn't a one-size-fits-all solution for performance though. Sometimes a better choice of algorithm may be an easier/bigger win.
And often a game will be GPU-bound on many machines, potentially due to less experienced developers not fully understanding the performance implications of all the engine features they're using (lighting, shadows, postprocessing). Unity and Unreal give you the power to turn on some pretty costly features with a few mouse clicks.
I don't avoid multi threading, but I avoid the problems it causes using MPSC where multiple threads create remote procedure call events, put them on a single threadsafe queue and the main thread consumes them in batches.
If you want to know how much this effects your program, just start a few threads that produce dummy procedure calls and measure the time it takes to perform them all on your main thread. It will usually be on the order of millions per second. Then compare it with calling the procedures from those threads, but introduce mutexes instead of queues.
Maybe this should be broken down by language used? Are indie devs proportionally using as much C++ as AAA games? If yes then you might have your answer ;)
I have zero clue how game engines like Godot or Unity play into this. How is multithreading in C#?
Disclaimer: I don't like multithreading in C++ but I've never ran into serious problems in Java. This is aside from any other points, just use (and avoiding pitfalls) of multithreading.
22 comments
[ 4.4 ms ] story [ 63.4 ms ] threadThe big one of those three when it's just my own projects is retrieving data. I tend not to work on complicated enough projects on my own that would require the other two all that often (and my side projects are usually games with relatively simple graphics).
Sometimes we do things on only one thread because its faster than doing it on all the threads.
https://lmax-exchange.github.io/disruptor/disruptor.html
A financial exchange does order matching at rates of millions of transactions per second on a single thread because its literally the fastest way to do things.
Would you consider operating the most important part of any financial exchange on a single thread "not interesting"?
The implicit concern (if you could not infer from the content of my link above) being that the developer community is completely blind to the performance capabilities of a single computer/core/thread in 2022 and are inclined to automatically skip over these simple solutions in favor of a multi-threaded solution.
Whenever I try to get a parallel speedup I build some kind of batching before I do the actual subtask execution because almost always the smallest level of granularity is too small so instead of getting a 4x speedup on four cores you get a 20x slowdown. Whenever I do this in a "hackathon" environment with others I end up feeling like a Cassandra because the other people think that batching is optional or an optimization but unless the natural granularity of your task is huge it's essential if your goal is getting speedup.
(Most people who try this, however, make it "not fun" and "not easy" by ignoring one essential fact which is that if you really want to get a speed-up you need to break the tasks into something that takes more time than it takes to switch a task! In the case of raytracing for instance, it probably takes more time to trace a single ray than it takes to switch tasks. If you bundled anywhere between 100 to 10,000 rays into a single task you'd probably get a good speedup. Often people have a fetish for some particular way to execute tasks in parallel and think that certain details of their approach are essential whereas the batching is essential to getting a speed-up if not to getting a correct answer.)
I work in Python a lot too, usually in Windows, and I am not in such a hurry to parallelize because the thread support in the language isn't natively strong (the GIL) and the more popular and mature multiprocessing libraries are frequently Unix only or only supported on Unix.
It sounds like you've got a specific example in mind, though.
If I were limited on time and resources, I would focus on improving things that affect the 99% of standard players rather than go and add multithreading and deal with ACID issues.
It's about making the project possible to complete within a reasonable timescale and on a small or non-existant budget.
Multithreading isn't a one-size-fits-all solution for performance though. Sometimes a better choice of algorithm may be an easier/bigger win.
And often a game will be GPU-bound on many machines, potentially due to less experienced developers not fully understanding the performance implications of all the engine features they're using (lighting, shadows, postprocessing). Unity and Unreal give you the power to turn on some pretty costly features with a few mouse clicks.
If you want to know how much this effects your program, just start a few threads that produce dummy procedure calls and measure the time it takes to perform them all on your main thread. It will usually be on the order of millions per second. Then compare it with calling the procedures from those threads, but introduce mutexes instead of queues.
What motivates your question?
I have zero clue how game engines like Godot or Unity play into this. How is multithreading in C#?
Disclaimer: I don't like multithreading in C++ but I've never ran into serious problems in Java. This is aside from any other points, just use (and avoiding pitfalls) of multithreading.