Live data from Hacker News

Optimizing a lock-free ring buffer

david.alvarezrosa.com

81–90 of 100 posts

Re: Optimizing a lock-free ring buffer

#81
post #62

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)

Thanks!

Re: Optimizing a lock-free ring buffer

#82
post #66

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

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.

Thanks for the explanation :)

Re: Optimizing a lock-free ring buffer

#83
post #71

Lock-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…

Lol. Funny story :)

Re: Optimizing a lock-free ring buffer

#84
post #6

From 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.

Ring buffers never get old. Here’s a useful mention of some of the most extensive technical work by LMAX team over 15 years ago https://martinfowler.com/articles/lmax.html

Re: Optimizing a lock-free ring buffer

#85
post #69

It'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 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

#86
post #10

Earlier 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…

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

#87
post #85
post #69

It'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…

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.

Re: Optimizing a lock-free ring buffer

#88
post #86

Earlier 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.

Right, that's what the various memory ordering constants are for in C++ atomics, and other languages will likely have an equivalent. On such architectures, those will emit special instructions or barriers.

Re: Optimizing a lock-free ring buffer

#89
post #87
post #85

Earlier 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 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 speed as the C++ version)

Re: Optimizing a lock-free ring buffer

#90
post #89
post #87

Earlier 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…

The GNU libstdc++ STL mutex is nothing but pthread_lock, so that's not a surprise.

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.

https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf

Post reply on HN