@JKSH said in Is QtConcurrent suitable for this concurrent/parallel algorithm?:
I wonder if you'll get a noticeable improvement if you use a non-atomic bool? Since the value always starts as false and the only value that could possibly be written is true, there's no benefit in guarding against race conditions.
Well indeed! Changing from std::atomic<bool> to just plain bool halved the time to run the algorithm/loop so that it is just the same as if the test were not there. So now I have some questions... :)
I got std::atomic<bool> from my friend ChatGPT, who is never wrong. He said I could/should use std::memory_order_relaxed. Reading up on that now:
Atomic operations tagged memory_order_relaxed are not synchronization operations; they do not impose an order among concurrent memory accesses. They only guarantee atomicity and modification order consistency.
Typical use for relaxed memory ordering is incrementing counters, such as the reference counters of std::shared_ptr, since this only requires atomicity, but not ordering or synchronization
So that part is something to do with the ordering of operations in different threads, which I do not need here. But what is the underlying behaviour of std::atomic<bool> with store() and load()? Does a plain std::atomic<bool> flag; flag = true; // or if (flag) ... behave any differently than using store()/load()? Ignoring this "memory order" stuff, which seems to be to do with ordering multiple accesses, is the terrible speed because atomic does some kind of "mutex" under the hood, not just a single/fast instruction to access it?
What exactly do I have to do/protect against concurrent access when? In the olden days, where "multitasking" meant a single CPU swapping between threads of execution, I had to think about something like the processor swapping in between each individual instruction, but I think not when right in the middle of executing a single instruction.
Now presumably a different thread can execute any time, even when the other thread is in mid-instruction? Why do I care anyway when I have, say, a bool or even int variable which I set in one thread and test in another? Those are (presumably) set in a single instruction, the reader just sees it either as it was before or afterwards but not in some inconsistent "mid-instruction" state? And here I don't care if a test in the loop sees a change from another thread this time round or next time round. I also don't think it matters if two threads simultaneously find their own factors and both set the variable to true (though I agree that might matter in an incrementing counter case)? Even if the shared variable (still one writer many readers) were a pointer wouldn't a statement which assigned a new value, ptr = new_value;, be a single instruction so a reader executing "during" that would still see the value either as before or after that statement, not half way through writing into the 4/8 bytes comprising the pointer?
Finally, while I am here: what exactly has to be done at runtime when you ask a thread to execute some code (function/lambda)? I assume there are two overheads. One is from initial construction of a thread/QThread. If you destroy and recreate you would pay this every time. But presumably with a thread pool like I am using it re-uses previously created threads? The second must be whatever has to be done to "start" or "run" the thread with its code, like QThread::start() or run() (say you have subclassed QThread::run() so it does not do any event loop via exec()). What has to be done/how much overhead is there to just get an (existing) thread to start executing some code? This is pretty significant if the code just does something really small and simple like a single division and then exits. Which is why I make each thread do a range of tests instead of one at a time, even though that complicates my algorithm.