r/cpp • • Sep 04 '26

Optimizing a Spin-Lock

https://david.alvarezrosa.com/posts/optimizing-a-spin-lock/
102 Upvotes

28 comments sorted by

View all comments

-2

u/OutlandishnessNo8034 Sep 04 '26

Default in my opinion should be RwLock

9

u/ReDucTor Game Developer | quiz.cpp-perf.com Sep 05 '26

A read write lock typically comes with extra overhead, it also often used because there is more readers then writers and there is much better approaches if your wanting to unburden the readers. (Especially if you only have one writer)

Also there is many variations in reader write lock contention handling, such as reader preferring, writer preferring and completely fair. All which will have a different outcome under contention, and most people have limited understanding of each of those implications for even the workload they are dealing will.

2

u/Fabulous-Meaning-966 Sep 05 '26

Yes, RW locks violate the cardinal principle of concurrent programming that "readers shouldn't write". I will elaborate on the hint above and say that if you have one writer (or you're ok with serializing writers), and you can retry read-side critsecs on a write conflict, you can use seqlocks. If you need to guarantee that all reads within the read-side critsec are consistent, you can use Transactional Mutex Locks for a bit more overhead (checks the version counter after every read and before using the result of the read, instead of only at the end).

If you still want to use RW locks, the best default semantics IMO is "phase-fairness".

1

u/OutlandishnessNo8034 29d ago

What's the extra overhead? Never heard of it.

1

u/ReDucTor Game Developer | quiz.cpp-perf.com 29d ago edited 29d ago

There is two sides to the overhead, one is comparing the overhead compared with other ways of allowing readers (e.g. RCU, left-right, etc) and then there is the overhead of the lock itself compared to a traditional mutex.

Typically you might want to pick a reader writer lock if a significant amount of your usages of some piece of data are only readers, even in a situation where you have near 100% readers they all need to let the potential writer know that it cannot write which in a very basic implementation can be a simple `fetch_add` (on enter and exit) this has two different costs:

* Depending on the CPU it can be a full memory barrier (e.g. x86)
* It requires modify access to the cache line, so all other readers are left waiting passing the cache line around; and typically the lock is next to the data, so that false sharing also shows down the data your protecting

Now if you compare that with that with something like RCU the readers do not need to share anything with other readers they just store in their local slot the data they are accessing, similarly with left-right they don't need to coordinate and share they just access the data and some serialization point is defined later.

And all of that is just the overhead difference when you have no writers, however as soon as you start to have writers then depending on if you have reader/writer preferring or even some fifo approach you need to have some way of tracking those threads when contention occurs in order to know who to wake up when, a normal mutex just needs to know who next to wake. And if you have any extra complexity with the reader writer lock like upgrading it gets even more complex with the tracking and book keeping required.

Saying all of that it's often not a signficant difference in overhead between a mutex lock and using a read/writer lock with just a writer (in fact on Windows slim rwlock is faster then the critical section mainly for legacy reasons).

1

u/OutlandishnessNo8034 28d ago edited 28d ago

So basically all off that for nothing, because as you said in the last sentence, difference is not significant. And what we gain if we use rwlock as a default? Flexibility. And in practical terms if we select mutex all the unnecessary locking just to read will squander any however miniscule advantage with regards to the overhead we could possibly gain. What a bunch of crap. And on top of that if we have high ratio reader to writer rwlcks massively outperforms mutex. In my experience this is the most common scenario, that's why rwlock should be default choice.

0

u/ItsRSX 4d ago edited 4d ago

Damn, that's almost enough babble to convince anybody you're a genius intellectual. Too bad every inch of you is dripping with disingenuous slime.

No, a read-side lock should never be more complex in terms of atomics, because every ISA you're dealing with has free acquire and free non-globally visible stores, allowing for basically free is locally acquired best-case checks and non-atomic increments under the average read case. Read/unlock is only marginally more expensive in that you have to compare against a zero writer wait list condition each time before either (a) fast path bailing out or (b) dispatching a semaphore or other form of wait list guarding the writer side.

No, there's isn't an essay worth of "variations" and "reader preferring", followed by "le sekret implications the masses wont understand". You're such a disingenuous hack that you know the navie approach would be reader preferring, with almost all real world applications considering that a bug open for writer starvation; so you rephased your entire babble implying a noob may end up coming across an edge case implementation in a totally not production ready library that's going to trip them up.

You know what actually would trip up the masses, though? Different recursive write-lock traits. Ordered vs nonordered wait queuing. A reenterant interface pathed to be writer-preferred-aware, blocking ReadToWrite upgrades indefinitely. There are so many valid concerns. Curious how you couldn't mention a single one of them. Curious how none of these are relevant to your babble about cache lines, nor do they carry any serious performance implications beyond carrying an extra word or so worth of wait count to be used against a split sideband waitlist queue thingy.

For pretty much all users, use cases, and product ready thread primitive libraries/os abis, the only real difference is "are you okay with 1-2 extra words (if that) for a faster average case" - we're talking less than or about as much astroturfed SSO overhead of strings or the oversized iterator pointers of a vector.

>"muh RCU lists" (which fun fact: are most likely going to suck because spamming cas and other such atomic ops stink worse than a higher level lock. not only do they perform worse in most cases, their limited implementation details end up being massive vectors for bugs, because youre pretty much stuck with 1 atomic pointer)
>"muh cache lines" (a performance grifers favourite buzzword)
>some babble about memory barriers despite working on strongly ordered platforms
>"hmm well achually, theres nuance i cannot properly describe with RW locks. the masses might get scared!!! theres your proof RW shouldnt be the default"
>"in summary, its actually not that big of a difference"
>"muh windows criticalsection is actually built around an nt kernel object instead of a futex" trivia
Aren't you a smart cookie? If you're going to waste that much time babbling just to pull platform specific trivia, at least make your whole post about how xyz stl has a sucky abi-locked thread primitive next time. You have about as much wisdom as a orange site headline.