Earlier quoted context omitted.
Thanks. Do you happen to know why hyperthreading should be disabled? In prod most trading companies do disable it, not sure about generic benchmarks best practices
It eliminates cache contention between siblings, which leads to increased latency (randomly)
Optimizing a lock-free ring buffer
81–90 of 100 posts
Re: Optimizing a lock-free ring buffer
#82Earlier quoted context omitted.
Thanks. Do you happen to know why hyperthreading should be disabled? In prod most trading companies do disable it, not sure about generic benchmarks best practices
There are some microarchitectural resources that are either statically divided between running threads, or "cooperatively" fought over, and if you don't need to hide cache miss latency, which is the only thing hyperthreading is really good at, you're probably better off disabling the supernumerary threads.
Re: Optimizing a lock-free ring buffer
#83Lock-free ring buffer is my favorite data structure. I remember implementing it in C++ and then using a legitimate implementation in the form of boost:SPSC for prod. The idea is so simple. And then I started thinking about designing some programming language or framework around the concept, only to then stumble upon the idea of "message passing" for concurrency. Which of course led me to learn about Erlang. And then…
Re: Optimizing a lock-free ring buffer
#84From 12M ops/s to 305 M ops/s on a lock-free ring buffer. In this post, I walk you step by step through implementing a single-producer single-consumer queue from scratch. This pattern is widely used to share data between threads in the lowest-latency environments.
Re: Optimizing a lock-free ring buffer
#85It's lock-free because it uses ordered loads and stores, which is also how you implement locks. I find the semantic distinction unconvincing. The post is really about how slow the default STL mutex implementation is.
The whole point of lock-free data structures and algorithms is that sometimes you can do better by using these atomic operations inside your own code, rather than using a one-size-fits-all mutex based on those same atomic operations.
(Note that I say "sometimes". Too many people believe that lock-free structures are always faster; as always, your mileage may vary. In this case it's a huge win, to the point where I would bet it almost always moves the bottleneck to the code actually using the ring buffer.)
Re: Optimizing a lock-free ring buffer
#86Earlier quoted context omitted.
Really? Pretty much all atomics i’ve used have load, store of various integer sizes. I wrote a ring buffer in Go that’s very similar to the final design here using similar atomics. https://pkg.go.dev/sync/atomic#Int64
They generally map directly to concepts in the CPU architecture. On many architectures, load/store instructions are already guaranteed to be atomic as long as the address is properly aligned, so atomic load/store is just a load/store. Non-relaxed ordering may emit a variant load/store instruction or a separate barrier instruction. Compare-exchange will usually emit a compare and swap, or load-linked/store-conditional…
Re: Optimizing a lock-free ring buffer
#87It's lock-free because it uses ordered loads and stores, which is also how you implement locks. I find the semantic distinction unconvincing. The post is really about how slow the default STL mutex implementation is.
That's what "lock-free" means. You still need to use the hardware mechanisms provided for atomicity. The whole point of lock-free data structures and algorithms is that sometimes you can do better by using these atomic operations inside your own code, rather than using a one-size-fits-all mutex based on those same atomic operations. (Note that I say "sometimes". Too many people believe that lock-free structures are a…
Re: Optimizing a lock-free ring buffer
#88Earlier quoted context omitted.
They generally map directly to concepts in the CPU architecture. On many architectures, load/store instructions are already guaranteed to be atomic as long as the address is properly aligned, so atomic load/store is just a load/store. Non-relaxed ordering may emit a variant load/store instruction or a separate barrier instruction. Compare-exchange will usually emit a compare and swap, or load-linked/store-conditional…
What's not guaranteed for "normal" loads and stores on many architectures is the order in which writes become visible to other CPU cores.
Re: Optimizing a lock-free ring buffer
#89Earlier quoted context omitted.
That's what "lock-free" means. You still need to use the hardware mechanisms provided for atomicity. The whole point of lock-free data structures and algorithms is that sometimes you can do better by using these atomic operations inside your own code, rather than using a one-size-fits-all mutex based on those same atomic operations. (Note that I say "sometimes". Too many people believe that lock-free structures are a…
My point is that the "huge win" is expressed in terms of a bogus and misleading baseline. The article moves immediately from the worst possible lock-based implementation to a pretty bad atomics-based implementation. The final punchline of the article is expressed as a ratio of the bad baseline. To make an honest conclusion, the article should also explore better ways of using the locks.
It's precisely the way we teach people how to build thread-safe systems. And we teach them to do it that way because we've learned from experience that letting them code up their own custom synchronization primitives leads to immense woe and suffering.
(and it's not slow because of the C++ mutex implementation, either - I tested a C/pthreads version, and it was the same speed as the C++ version)
Re: Optimizing a lock-free ring buffer
#90Earlier quoted context omitted.
My point is that the "huge win" is expressed in terms of a bogus and misleading baseline. The article moves immediately from the worst possible lock-based implementation to a pretty bad atomics-based implementation. The final punchline of the article is expressed as a ratio of the bad baseline. To make an honest conclusion, the article should also explore better ways of using the locks.
It's not a "bogus and misleading baseline". It's precisely the way we teach people how to build thread-safe systems. And we teach them to do it that way because we've learned from experience that letting them code up their own custom synchronization primitives leads to immense woe and suffering. (and it's not slow because of the C++ mutex implementation, either - I tested a C/pthreads version, and it was the same spe…
I really don't understand what you are saying about not using custom primitives. The whole article is "YOLO your own synchronization" and it fails to grapple with the subtleties. An example of the unaddressed complexity: use of acquire-release semantics for head_ and tail_ atomics imposes no ordering whatsoever between observations of head_ and tail_. The final solution has four atomics that use acquire-release and does not discuss the fact that threads may observe the values of these four things in very surprising order. The issue is so complex that I consider this 50-page academic paper to be the bare minimum survey of the problem that a programmer should thoroughly understand before they even consider using atomics.