2026-07-17
You wrote a spinlock. It works. Then you profiled it and found it burns 100% of a core doing nothing useful, wrecks the sibling hyperthread, and triggers a memory-ordering machine clear every time the lock is released. The fix is a single instruction: PAUSE (x86) or YIELD (ARM). It's the closest thing a CPU has to "please stop being so eager."
PAUSE does three things at once:
while (*lock) {} fills the load queue with speculative reads of the same cache line. When another core writes the lock, every one of those in-flight loads is a memory-order violation — the CPU flushes the pipeline (a memory ordering machine clear), costing 20-40 cycles per release. PAUSE tells the load queue "don't bother."IA32_UMWAIT_CONTROL) for the max wait time.Concrete example: The Linux kernel's cpu_relax() macro is literally rep; nop (the encoding of PAUSE — a NOP prefixed with REP that older CPUs treated as a normal NOP). Every kernel spinlock, RCU wait loop, and busy-wait uses it. Removing it from queued_spin_lock_slowpath in a stress test roughly doubles contention latency and tanks sibling-thread throughput by 30-50%.
Rule of thumb for spin loops:
sched_yield() or a futex wait — you're burning cache bandwidth for nothing.ARM's YIELD is weaker — it's purely a hint, and many implementations treat it as a NOP. ARM's real spin-wait primitive is WFE (Wait For Event), which parks the core until another core executes SEV (Send Event) or a monitored address changes. Much lower power than PAUSE, but requires cooperation from the releasing thread.
