Thread Pinning: CPU Affinity and Performance Isolation


Why Cores Matter

Modern servers pack dozens or hundreds of CPU cores into a single system. The operating system scheduler moves threads between these cores constantly, balancing load and maximizing throughput. For most workloads, this dynamic scheduling works well. But for latency-sensitive applications—trading systems, real-time analytics, game servers, high-frequency networking—the overhead of thread migration creates unpredictable performance spikes that can’t be tolerated.

Thread pinning, also called CPU affinity or core binding, locks specific threads to specific CPU cores. This gives up scheduler flexibility in exchange for predictability and lower latency. The tradeoff is worth it when microseconds matter.

The Cost of Migration

When a thread moves from one core to another, its cache state doesn’t follow. The L1 and L2 caches that held hot data and instructions are now on a different core. The thread has to rebuild its working set from scratch, fetching from L3 cache or main memory—orders of magnitude slower than L1. This cache pollution affects not just the migrated thread but also the threads already running on the destination core, whose cache lines get evicted to make room.

NUMA architectures add another layer of complexity. On multi-socket systems, memory is physically closer to some cores than others. A thread migrated to a core on a different socket now accesses its memory across an interconnect, adding latency and consuming bandwidth. The scheduler tries to avoid this, but under load it will sacrifice NUMA locality for CPU utilization.

Context switching compounds the problem. Even if a thread stays on the same core, being preempted by other threads thrashes the cache. Pinning eliminates competing threads, giving the critical workload exclusive access to the core’s execution resources and caches.

How Pinning Works

On Linux, CPU affinity is set via sched_setaffinity() or the taskset command. You specify a bitmask of allowed cores. The thread will only ever be scheduled on those cores. Most commonly, you pin one thread to one core for complete isolation.

cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(4, &cpuset);  // Pin to core 4
pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset);

On NUMA systems, you also want to ensure memory is allocated on the correct socket. The numactl tool and numa_alloc_onnode() API handle memory placement, keeping data physically close to the cores that access it.

Some systems go further and isolate cores at boot time using the isolcpus kernel parameter. This removes cores from the scheduler’s pool entirely, preventing any system threads or other processes from using them. The application then has exclusive, uninterrupted access.

Where Pinning Wins

Financial trading systems pin order processing threads to isolated cores to minimize tail latency. Every microsecond counts when competing for liquidity, and scheduler jitter is unacceptable.

Database systems pin network I/O threads, query executors, or transaction coordinators to specific cores, reducing cross-core communication overhead and improving throughput on high-concurrency workloads.

Real-time media processing—video encoding, audio synthesis—uses pinning to guarantee frame deadlines. A missed deadline causes visible artifacts; pinning eliminates the scheduler as a source of unpredictability.

Packet processing systems like DPDK pin threads to cores and use polling instead of interrupts, bypassing the kernel entirely. Combined with huge pages and dedicated NICs, this achieves line-rate performance with deterministic latency.

The Tradeoffs

Pinning sacrifices CPU utilization. A pinned thread can’t use idle cores elsewhere in the system, even if its assigned core is busy. You’re paying for those cores whether you need them or not.

It also increases operational complexity. You need to understand your hardware topology—core counts, NUMA layout, cache sizes—and match thread placement to workload characteristics. Misconfiguration can make performance worse.

For most applications, the scheduler’s flexibility is more valuable than the latency reduction from pinning. But when predictability matters more than efficiency, binding threads to cores gives you control the scheduler can’t provide.