CPU affinity

On this page 7

CPU affinity is the property of a process or thread that defines the set of logical CPUs on which the scheduler may run it. In Linux process management, affinity is stored as a bit mask per task, and the kernel scheduler never places the task on a CPU outside that mask.

By default every task may run on every online CPU, and the scheduler moves work between cores to balance load. Setting an explicit affinity, often called pinning, trades that flexibility for cache locality, predictable latency and workload isolation, which matters for databases, network processing, real-time systems and virtual machines.

How the scheduler applies the affinity mask

Each task in the kernel carries a `cpus_allowed` mask with one bit per logical CPU. When the Completely Fair Scheduler or its successor EEVDF selects a run queue, and when the load balancer migrates tasks between cores, both consult this mask and skip any CPU whose bit is clear.

Affinity is inherited across `fork()`, so a mask set on a parent shell applies to every program it launches. It applies per thread: in a multithreaded program, each thread has its own mask, and tools that operate on a process ID must be told explicitly to update all threads of the process.

The kernel distinguishes two strengths of affinity. Soft affinity is the scheduler’s natural tendency to keep a task on the CPU where it last ran, because its data is still warm in the cache. Hard affinity is the explicit mask that forbids other CPUs entirely.

Tools and interfaces for setting affinity

Linux exposes CPU affinity through system calls, command-line tools and service settings. Which one to use depends on whether the setting is temporary, part of an application or part of system configuration.

InterfaceLevelExample
tasksetCommand line, existing or new processtaskset -c 2,3 ./server
sched_setaffinity()System call from application codeCalled with a cpu_set_t mask
pthread_setaffinity_np()Library call for one threadPinning a worker thread
CPUAffinity=systemd unit or system defaultCPUAffinity=4-7
numactlCommand line, CPUs and memory nodesnumactl --cpunodebind=0 --membind=0
cpuset cgroupGroup of processescpuset.cpus set to 8-15
Interfaces for controlling CPU affinity

taskset -p shows the mask of a running process in hexadecimal form, so f means CPUs 0 to 3, while taskset -cp shows it as a readable CPU list. To change every thread of a running program, the `-a` option is required, otherwise only the main thread is updated.

Affinity decides where a task may run, whereas priority and nice values decide how much CPU time it receives. Several neighboring mechanisms in the processes topic are easy to confuse with affinity, and they are often combined in practice.

ControlWhat it governsScope
CPU affinityWhich CPUs a task may useThread or process
Nice valueRelative share of CPU timeThread or process
Real-time priorityStrict preemption orderThread
Cgroup cpusetAllowed CPUs and memory nodesGroup of processes
Cgroup cpu.maxHard quota of CPU timeGroup of processes
isolcpus and nohz_fullCPUs removed from general schedulingWhole system, at boot
Affinity and neighboring process controls

The cpuset cgroup sets an outer limit, and a task’s own affinity mask is effectively intersected with it. A container restricted to CPUs 8 to 15 therefore cannot pin a thread to CPU 2, and container runtimes use exactly this mechanism to implement options such as `–cpuset-cpus`.

When pinning helps and how to plan it

Pinning helps when cache warmth, memory locality or interference dominate performance. It rarely helps general workloads, because the scheduler already balances load well. The following decision table summarizes typical cases.

SituationRecommended choiceReason
General web or desktop workloadLeave the defaultLoad balancing outperforms static pinning
Multi-socket database serverBind to one NUMA node with memoryAvoids slow remote memory access
Packet processing with DPDKPin each poll thread to an isolated coreRemoves jitter and context switches
Latency-critical real-time taskPin to cores in isolcpus or nohz_fullKeeps housekeeping work away
Virtual machine vCPUsPin vCPUs to dedicated host coresStable performance and cache reuse
Noisy batch jobsConfine to a subset of coresProtects interactive services
Deciding whether to set CPU affinity

Planning requires knowledge of the CPU topology. Tools such as lscpu -e and lstopo show which logical CPUs are hyperthread siblings, which share an L3 cache and which belong to each NUMA node. Pinning two busy threads to sibling hyperthreads of one core often performs worse than leaving them unpinned.

Interrupt affinity complements process affinity. Network card queues can be steered through `/proc/irq/N/smp_affinity`, so that interrupts for a queue are handled on the same core as the thread consuming its packets, which keeps data in the same cache hierarchy.

Risks and common mistakes

Static affinity removes the scheduler’s ability to adapt, so mistakes show up as idle cores next to overloaded ones. The most frequent problems are listed below.

  • Pinning too many tasks to one core: they compete for it while other cores stay idle.
  • Ignoring hyperthread siblings: two logical CPUs on one physical core share execution units.
  • Pinning CPUs without matching memory: on NUMA systems, remote memory erases the cache benefit.
  • Assuming stable CPU numbering: numbering differs between machines and can change with firmware settings.
  • Forgetting the -a option: only the main thread moves, while worker threads keep the old mask.

Affinity is also not a security boundary on its own. An unprivileged process can change its own mask within its cpuset, so isolation that must hold against the workload itself belongs in cgroups configured by the administrator.

The short version

A modern computer has many processor cores, like a kitchen with many cooks. Normally any cook may pick up any order. CPU affinity is a rule that says this order may only be prepared by these particular cooks.

That can be useful because a cook who keeps working on the same dish already has the ingredients at hand, so the work goes faster. But if too many orders are assigned to the same few cooks while others stand idle, everything slows down. Affinity helps only when it is planned carefully.

Where it makes a difference

A telecom company runs a packet-processing application on a Linux server with two sockets and 32 cores per socket. Throughput is acceptable, but latency spikes of several hundred microseconds appear whenever background services wake up on the same cores as the forwarding threads.

The engineers reserve cores 2 to 15 on the socket attached to the network card with the isolcpus and nohz_full boot parameters. They pin each forwarding thread to one of those cores, steer the card’s interrupts to the same socket and allocate packet buffers on the local NUMA node with numactl. All system services receive CPUAffinity=0-1,16-63 in the systemd configuration.

After the change, latency spikes disappear from the forwarding path, throughput rises because packet data stays in the local cache, and the remaining services run undisturbed on the other cores.