Measuring Latency with the TSC
What a TSC reading means, when it is taken, which core it is taken on, how to record timestamps on the hot path, and how ticks become ns: where the frequency comes from and how to calibrate it yourself.
Tags
22 posts
What a TSC reading means, when it is taken, which core it is taken on, how to record timestamps on the hot path, and how ticks become ns: where the frequency comes from and how to calibrate it yourself.
From one mutex to many producers: condition variables and lost wakeups in a blocking queue, rte_ring’s two pairs of indices and in-order publishing, Disruptor’s availability flags, and whether a full queue should block, drop, or …
Wait-free is not a faster lock-free: it turns a failed CAS from “try again” into “I come after you”, which bounds every thread’s steps, at the cost of more atomics and code that is harder to get right.
stat for the totals, record and report to find the function holding 98% of the samples, annotate to see every sample land on the instruction after an add: a chain of adds, each waiting for the last, and splitting it four ways is …
Whether a spinlock is fast depends on what its waiters do: if every attempt is a read-modify-write, they drag the lock’s line back and forth. Read before you grab, put PAUSE in the loop, back off after losing, and waiting costs …
One tick touches 140 cache lines, and most of the time goes into waiting for them. None of the four steps changes the computation; they change when data arrives, where it lands and who holds it: put the misses in flight together, …
The compiler only makes rewrites it can prove don’t change the result, and it does constant multiplies and divides better than you. What’s left for you is where it lacks information or where the rewrite changes the result: …
How far the compiler can optimize depends on what it can see and what it dares to assume: different types are treated as non-overlapping, an invisible function body means arguments go by the calling convention, and helper macros …
There are three ways to get data into cache ahead of time, each with a precondition: prefetching needs you to know the address before the hardware does, warming has to happen on the core that reads the data, and double buffering …
One instruction computes a row of numbers only if the compiler knows how wide a register it may use, is sure nothing overlaps, and is allowed to reorder; when it can’t get all three, intrinsics let you write it yourself, at the …
In a loop like a[i] = *b, the compiler re-reads *b every iteration, because writing a[i] might change it. Pointer optimization is about letting the compiler read less, and fitting linked structures into nearer caches.
Returning a struct by value is usually free, so there’s no need for out-parameters; what to watch are the few patterns that always move or copy. Pure-function attributes are a promise to the compiler: right, and it calls less; …
The base class writes the flow and the subclass fills in the details: virtual functions pick the subclass at run time, CRTP at compile time. What you save is more than one indirect jump: the call inlines completely and the object …
A dot-product loop reports “vectorized” at -O3, yet its four additions still wait on each other: floating-point addition isn’t associative, so the compiler won’t reorder it. What loop optimization really asks of you is to spot …
One memory access takes about 100 ns and brings back 64 B. One at a time, that’s 0.64 GB/s, yet one core reading sequentially gets 15–60 GB/s. The missing factor is the number of requests in flight. Whether the next load’s address …
The front end has to fetch instructions every cycle, but a branch isn’t resolved until a dozen-plus cycles later, so the CPU guesses. A right guess costs almost nothing; a wrong one throws away everything after it, about 15–20 …
MESI enforces one rule: a cache line has either one core that may write it or any number of cores that may only read it, never both. The four states are the answers to two questions — does anyone else hold this line, and have I …
Market data has one producer and several consumers, each of which must see every message, and the producer is the hot path that may not wait for anyone. The answer is a broadcast ring: every slot carries its own sequence stamp, …
An access never pays for bytes; it pays for cache lines, and the address decides which lines, which cache set they compete for, and which DRAM channel and bank they live on. Part 1 is alignment: why a double aligned to 8 never …
A lock-free SPSC queue works because every shared variable has exactly one writer: each side updates only its own counter and merely reads the other’s. The rest is keeping that property from being quietly undone — a shared element …

Every technique in this post does the same thing at a different layer: move an uncertain, kernel-involving operation off the hot path and force it to happen once, upfront, before it can ambush a single request. An object pool …

The store buffer and the cache-coherence protocol are one piece of hardware, but they create two unrelated engineering problems. One is correctness: does a thread see the right value, in the right order — solved by memory_order …
