lowlatency · advanced

CPU Affinity, Thread Pinning & NUMA

By default the scheduler may migrate a thread between cores, cooling its L1/L2 caches and adding jitter, so pinning a thread to a fixed core with CPU affinity (pthread_setaffinity_np or sched_setaffinity) keeps its working set warm and latency predictable; for the most critical threads you also isolate the core (isolcpus/cpuset) so nothing else is scheduled there, the basis of thread-per-core designs. On multi-socket servers, RAM is divided into NUMA nodes each attached to a socket: a core accessing its local node is fast, while reaching a remote node crosses the inter-socket interconnect with higher latency and less bandwidth. Linux uses a first-touch policy, placing a page on the node of the thread that first writes it, so you should initialize data on the same thread/node that will use it or bind memory and threads together with numactl/libnuma — a thread that allocates and then hands data to another node is a classic NUMA mistake. The overarching goal of affinity and NUMA tuning is determinism — minimizing worst-case tail latency and jitter rather than maximizing throughput — and it combines with avoiding context switches (busy-polling instead of sleeping on the critical path), accounting for hyperthread siblings that share execution units, and steering interrupts away from hot cores. Because these knobs help some workloads and hurt others, every change must be measured.

🔑 Key line

Pin latency-critical threads to fixed (ideally isolated) cores so caches stay warm and latency is predictable, avoiding migration jitter. On multi-socket NUMA boxes, remote-node memory is slower, so place data (first-touch) and the thread on the same node. The goal is deterministic tail latency, not throughput — and every change must be measured.

The code

// Pin a thread to a specific core (Linux):
cpu_set_t set; CPU_ZERO(&set); CPU_SET(2, &set);
pthread_setaffinity_np(thr, sizeof set, &set); // run only on core 2
// NUMA: on multi-socket boxes, memory is attached to a node.
// local node access = fast
// remote node access = slower (cross-socket interconnect)
// Allocate memory on the node where the thread runs (first-touch).

What this lesson walks through

  1. 01Pin a thread to a core — kill migration jitter
  2. 02NUMA — memory has a home node
  3. 03Gotcha — pinning without NUMA awareness
  4. 04The goal — predictable tail latency

By default the scheduler migrates threads between cores, cooling their caches and adding jitter. Pinning a thread to one core (pthread_setaffinity_np) keeps its caches warm and its latency predictable.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of CPU Affinity, Thread Pinning & NUMA and 100+ animated C++ interview lessons.

← Previous
Memory Pools & Arena Allocators
Next →
Branch Prediction & Branchless Code