Atomic polling intervals for highly concurrent workloads
byronwasti.com
byronwasti.com
It depends, of course, on how the mutex is implemented, but yes, this is the reality a lot of times.
The thing about atomics is that they can appear simple to naïve eyes, but the semantics are very difficult to get right (especially if you want your code to be portable across architectures).
Using the memory ordering relaxed basically only guarantees there won't be word tearing.
For example consider the following:
// T1:
r1 = load_relaxed(y); // A
store_relaxed(x, r1); // B
// T2:
r2 = load_relaxed(x); // C
store_relaxed(y, 42); // D
Which implies that it's legal to read r1 == r2 == 42, because even if:- in T1 A happens before B (A ~> B)
- in T2 C happens before D (C ~> D)
nothing prevents D from happening before A in the modification order of y and C happening before B on the modification order of x.
In any case, on Intel processors, most accesses not crossing cache line boundaries are somewhat atomic beyond relaxed, which brings about another phenomena: false sharing.
Which essentially boils down to a performance penalty due to two threads accessing data on the same cache line, even if it's not the same word.
Another thing that can affect measurements like this is task/core affinity. The cost to coordinate cache line synchronization is different if both are on the same core but different hyper-threads, two cores on the same socket (with some variation on which cores) and if they are on different sockets (big/little architectures bring their own complications too).
In the article's example, it would be valid for the counter to never be flushed (not on x86), as long as there's enough cache capacity, effectively making the polling thread read always the same number.
Although this is very unlikely on realistic workloads.
I would have used release/acquire semantics to get predictable capture of data and a not too aggressive polling behavior so as not to compete for CPU caches with useful workloads.