Tips: Don't mutate. Prefer pure functions over functions that mutate state and parameters. Ideally you interact with your system by giving it input and having it return results, not change some state somewhere.
For example, It's not uncommon to see code that looks something like this
In the rush to try and avoid copying values or allocating, this program is now hard to multithread safely. You might want `stuff` to be shared and filled up across threads but that's not possible to safely do without a bunch of locks.
Now to get threading, it's easy to split `values_to_compute` up to manageable chunk sizes and send them pinging down the system for as many cores as you have available. Then you can aggregate the results at the end.
The way to think about these things is primarily about limiting shared mutable state among functions as much as possible. A rule of thumb is if you have functions which return `void` you are potentially making a headache for concurrency down the road.
The functional style is nice and all, but once you’ve reaped the low hanging fruits some problems are very hard or cumbersome to tackle in a stateless fashion.
I think this is bad advice, the lead to multiple dead ends. Especially in the fields I know, video games and embedded software, state and mutation are everywhere.
Embrace the state, parallelise your code instead of making it concurrent. Easier, almost always faster and more robust.
This could very much be that different applications have different strategies that are applicable.
For games and embedded systems, memory and CPU cycles are at a premium. My strat works best in languages with a GC where memory is abundant and cheap to allocate and there's not strict CPU time requirements.
For the field I'm familiar with, microservices and backends, this works really well and mutation can generally be controlled (usually the only permanent state is the database).
I agree shared state is probably faster, I'm not sure I agree with it being robust. Parallel code with shared state can be deceptively tricky to get right, especially when trying to hold multiple locks at the same time (which you have to do if you want to be fast). Nobody likes debugging why an app suddenly freezes for no apparent reason (deadlock).
Split/join style. It is almost like a single threaded code, except that all heavy work is done as fast as possible in parallel, the single threaded part is almost free in practice, less than 1ms in total per frame, often 10 times less.
Split/join is also what I was talking about but storing the values in immutable temporary heap locations rather than in a common lockless queue.
If you have the memory and allocation speed, that can be much faster than a lockless queue (especially if that queue is under high contention). However, for a game and embedded system I'd imagine the impact on heap fragmentation would be a pretty significant reason not to do this.
For a GCed language, those heap allocations are fairly cheap, quiet a bit cheaper than what you pay for in a managed memory language (In GCed languages, the majority of memory allocations are 1 or 2 conditional checks with a pointer bump. It's very nearly the same speed as stack allocation).
When I was talking about mutable state (and my example is perhaps bad), I'm more talking about system or process wide mutable state.
Do mutable things inside your function. Have an immutable public interface.
Also, split state so that it's not shared mutably among threads, and share immutable views when you need other threads to see it.
For example, It's not uncommon to see code that looks something like this
In the rush to try and avoid copying values or allocating, this program is now hard to multithread safely. You might want `stuff` to be shared and filled up across threads but that's not possible to safely do without a bunch of locks.The better solution looks something like this
Now to get threading, it's easy to split `values_to_compute` up to manageable chunk sizes and send them pinging down the system for as many cores as you have available. Then you can aggregate the results at the end.The way to think about these things is primarily about limiting shared mutable state among functions as much as possible. A rule of thumb is if you have functions which return `void` you are potentially making a headache for concurrency down the road.