E

Erik Rigtorp

rigtorp.se · Personal blog · on HN since 2020

Erik Rigtorp writes about low-latency C++ programming, covering ring buffers, spinlocks, virtual memory, atomics, and system tuning.

What HN saysHN links it in discussions of atomics, memory ordering and benchmarks; commenters give no direct judgment of the blog itself beyond one finding a humorous GIF on the homepage.
21HN people shared it
11linked it in comments
13stories submitted
435story points
4 months agolast shared
2020people per year2026

Most-shared posts

  1. 1
    Optimizing a Ring Buffer for Throughput
    9 people · 9 points · first shared 2022 · discussion on HN
  2. 2
    Latency Implications of Virtual Memory
    5 people · 10 points · first shared 2020 · 1 comments on HN
  3. 3
    Correctly implementing a spinlock in Modern C++
    4 people · 118 points · first shared 2020 · 97 comments on HN
  4. 4
    Low latency tuning guide
    3 people · 294 points · first shared 2020 · 98 comments on HN
  5. 5
    Aligned AVX loads and stores are atomic
    2 people · 3 points · first shared 2021 · discussion on HN
  6. 6
    Split locks
    2 people · first shared 2020
  7. 7
    Tips for Using the Sockets API
    1 people · 1 points · first shared 2020 · discussion on HN
  8. 8
    Fuzzing floating point code
    1 people · first shared 2020
  9. 9
    Homepage
    1 people · first shared 2022
  10. 10
    Hashing
    1 people · first shared 2021

What HN people wrote when they linked it

virgulino · 2022-09-18
I went to your homepage. Your gif "Programming in C++" made me really laugh, thanks for that! 8-) https://rigtorp.se/
eklitzke · 2023-08-20
The blog post is also kind of unhinged because in the incredibly rare cases where you would want to write code like this you can literally just use the asm keyword. I think it's also worth considering WHY compilers (and the C standard) make these kinds of assumptions. For starters, not all hardware platforms allow unaligned accesses at all. Even on x86 where it's supported, you want to avoid doing unaligned reads at all costs because they're up to 2x slower than aligned accesses. God forbid you try to use unaligned atomics, because while technically supported by x86 they're 200x slower than using the LOCK prefix with an aligned read.[^1] The fact that you need to go through escape hatches to get the compiler to generate code to do unaligned loads and stores is a good thing, because it helps prevent people from writing code with mysterious slowdowns. Writing a function that takes two pointers of the same type already has to pessimize loads and stores on the assumption that the pointers could alias. That is to say, if your function takes int p, int q then doing a store to p requires reloading q, because p and q could point to the same thing. Thankfully in some situations the compiler can figure out that in a certain context p and q have different addresses and therefore can't alias, this helps the compiler generate faster code (by avoiding redundant loads). If p and q are allowed to alias even when they have different addresses, this would all go out the window and you'd…
anonymoushn · 2022-11-27
There are a lot of issues here, so I can share some stuff about some of them and hope that some helpful internet commenters come along and point out where I have neglected important things. A single modern CPU core is superscalar and has a deep instruction pipeline. With your help, it will decode and reorder many instructions and execute many instructions concurrently. Each of those instructions can operate on a lot of data. As famous online controversial opinion haver Casey Muratori tells us, most software just sucks, like really really bad (e.g. commonly people will post hash table benchmarks of high-performance hash tables that do bulk inserts in ~100ns/op, but you can do <10ns/op easily if you try), and using SIMD instructions is table stakes for making good use of the machinery inside of a single CPU core. SIMD instructions are not just for math! They are tools for general purpose programming. When your program needs to make decisions based on data that does not contain obvious patterns, it is often a lot cheaper to compute both possible outcomes and have a data dependency than to have a branch. Instructions like pshufb or blendv or just movemask and then using a dang lookup table can replace branches. Often these instructions can replace 32 or 64 branches at a time[0]. Wojciech Muła's web site[1] is the best collection of notes about using SIMD instructions for general-purpose programming, but I have found some of the articles to be a bit terse or sometimes…
scrubs · 2022-09-20
This benchmark reminds me of "ffwd: delegation is (much) faster than you think" https://www.seltzer.com/margo/teaching/CS508-generic/papers-a1/roghanc… This paper describes a mechanism for client threads pinned to a distinct cores to delegate a function call to distinguished server thread pinned to its own core all on the same socket. This has a multitude of applications the most obvious one making a shared data structure MT safe through delegation rather than saddling it with mutexes or other synchronization points especially beneficial with small critical sections. The paper's abstract concludes claiming "100% [improvement] over the next best solution tested (RCL), and multiple micro-benchmarks show improvements in the 5–10× range." The code does delegation without CAS, locks, or atomics. The efficacy of such a scheme rests on two facets, which the paper explains: * Modern CPUs can move GBs/second between core L2/LLC caches * The synchronization between requesting clients and responding servers depends on each side spinning on shared memory address looking for bit toggles. Briefly, servers only read client request memory which the client only writes. (Clients each have their own slot). And on the response side client's read the servers shared response memory, which only the server writes. This one-side read, one-side write is supposed to minimize the number of cache invalidations and MESI syncs. I spent some time testing the author's code and went so far as…
raphlinus · 2021-07-06
A GPU followup to this article. While on CPU sequentially consistent semantics are efficient to implement, that seems to be much less true on GPU. Thus, Vulkan completely eliminates sequential consistency and provides only acquire/release semantics[1]. It is extremely difficult to reason about programs using these advanced memory semantics. For example, there is a discussion about whether a spinlock implemented in terms of acquire and release can be reordered in a way to introduce deadlock (see reddit discussion linked from [2]). I was curious enough about this I tried to model it in CDSChecker, but did not get definitive results (the deadlock checker in that tool is enabled for mutexes provided by API, but not for mutexes built out of primitives). I'll also note that using AcqRel semantics is not provided by the Rust version of compare_exchange_weak (perhaps a nit on TFA's assertion that Rust adopts the C++ memory model wholesale), so if acquire to lock the spinlock is not adequate, it's likely it would need to go to SeqCst. Thus, I find myself quite unsure whether this kind of spinlock would work on Vulkan or would be prone to deadlock. It's also possible it could be fixed by putting a release barrier before the lock loop. We have some serious experts on HN, so hopefully someone who knows the answer can enlighten us - mixed in of course with all the confidently wrong assertions that inevitably pop up in discussions about memory model semantics. [1]:…
rigtorp · 2026-07-09
That looks like a rewrite of my earlier work: https://rigtorp.se/ringbuffer/
dataflow · 2025-04-11
> the fundamental limit for atomic writes is usually the register-size for a CPU which is usually 64 or 32 bits CPUs nowadays support double the largest general-purpose register width. Unofficially, some CPUs can also do twice that: https://rigtorp.se/isatomic/
BazookaMusic · 2023-04-24
What you're describing about writing a mutex with cmpxchg is a spinlock. If you just use that operation it's also a very inefficient spinlock. The point of a lock free algorithm is to avoid having a thread wait on a resource and not do any work. It may do useless work that needs to be reverted, but it's not sleeping or spinning until something happens. It's not guaranteed that you can do that for every algorithm in a way that makes sense performance wise. Here's a nice resource I used on my thesis for an adequate but not perfect spinlock with atomic instructions: https://rigtorp.se/spinlock/
anthomtb · 2022-05-15
https://rigtorp.se/ringbuffer/, from a comment below, answered my question. In rough terms, every push or pop from the queue also causes head or tail to be written, thus dirtying a line of cache. That’s the bit I misunderstood.
ajtulloch · 2021-11-16
https://rigtorp.se/notes/hashing/ and links therein (https://github.com/martinus/better-faster-stronger-mixer ) are almost certainly preferable to this mixer.
indiosmo · 2025-05-31
bpftrace was instrumental in helping me diagnose a latency spike on a hot path. I call into a vendor library that eventually calls into Solarflare's TCPDirect API to send packets to the network. This call usually stays under 5us and has very little variance, but I was seeing the occasional 30ms or 60ms outlier. I attached probes to track the latency of each call down the stack and it became quite easy to pin down the specific function. Eventually tracking it down to a single assignment to a variable that was mmapped to a file, and getting spikes due to page cache writeback. After figuring it out I found these two articles that go into more detail on the problem. https://rigtorp.se/virtual-memory/ https://tungdam.medium.com/our-lessons-on-linux-writeback-do-dirty-job… Here's one of the probes producing a histogram of the latency of the call to the TCPDirect function, which was surprising in how simple it was. sudo bpftrace -p "$PID" -e ' // —— TCPDirect —— uprobe:/lib/x86_64-linux-gnu/libonload_zf.so.1:zft_send_single { @zf_start[tid] = nsecs; } uretprobe:/lib/x86_64-linux-gnu/libonload_zf.so.1:zft_send_single /@zf_start[tid]/ { $lat = nsecs - @zf_start[tid]; @h_zft = hist($lat / 1000); delete(@zf_start[tid]); } '

Keyboard shortcuts

/Search
j kNext / previous blog
EnterOpen selected blog
vVisit the blog site
fFollow / unfollow
lSwitch grid / list view
dSwitch dark mode
g hHome
g pTop posts
g tTopics
g fFollowing
g lLiked
g yHistory
g qFAQ
EscBack / close