CPU Affinity in Linux: How to Pin Processes to Specific Cores for Optimal Performance


2 views

In multi-core systems, Linux by default uses all available CPU cores for process scheduling. However, there are scenarios where you might want to restrict certain processes to specific cores:

  • Preventing cache pollution in performance-critical applications
  • Isolating workloads for better predictability
  • Creating dedicated cores for real-time processes
  • Testing multi-core application behavior

Linux provides several ways to control CPU affinity:

1. taskset Command

The most straightforward method is using the taskset utility:


# Launch a process pinned to CPU 0
taskset -c 0 ./my_process &

# Set affinity for existing process (PID 1234) to CPUs 0 and 1
taskset -cp 0,1 1234

2. sched_setaffinity System Call

For programmatic control, use the sched_setaffinity system call:


#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>

int main() {
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);  // Use only CPU 0
    
    if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
        perror("sched_setaffinity");
        return 1;
    }
    
    // Process now runs only on CPU 0
    while(1) { /* work */ }
    return 0;
}

For more sophisticated control:

Using cgroups


# Create a cgroup
mkdir /sys/fs/cgroup/cpuset/my_group

# Set allowed CPUs
echo 2-3 > /sys/fs/cgroup/cpuset/my_group/cpuset.cpus

# Add process to cgroup
echo <pid> > /sys/fs/cgroup/cpuset/my_group/tasks

Kernel Boot Parameter

For permanent core isolation, add to your kernel boot parameters:


isolcpus=2,3  # Reserve CPUs 2 and 3 for exclusive use

Check process CPU affinity with:


taskset -p <pid>
# Or
cat /proc/<pid>/status | grep Cpus_allowed

Monitor CPU usage with:


top -H -p <pid>  # Show thread-level CPU usage
mpstat -P ALL 1  # Monitor per-CPU utilization
  • NUMA architecture awareness is crucial for memory-bound processes
  • Hyperthreading cores share resources - pinning to physical cores might be preferable
  • Over-pinning can lead to underutilization
  • Consider process priorities (nice values) alongside affinity

When dealing with multi-core systems, Linux provides robust mechanisms for controlling process scheduling through CPU affinity. This feature allows administrators and developers to bind processes to specific CPU cores, which is particularly useful for:

  • Optimizing cache utilization
  • Reducing context switching overhead
  • Isolating critical processes
  • Benchmarking performance

The primary methods for managing CPU affinity include:

1. taskset Command

The simplest way to set CPU affinity is using the taskset utility:


# Start a process bound to CPU 0
taskset -c 0 ./my_process &

# Change affinity of running process (PID 1234) to CPUs 0 and 2
taskset -pc 0,2 1234

2. sched_setaffinity System Call

For programmatic control, use the sched_setaffinity system call:


#define _GNU_SOURCE
#include <sched.h>

cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(0, &mask);  // Use only CPU 0

if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
    perror("sched_setaffinity");
}

Isolating CPU Cores at Boot

For maximum isolation, add to your kernel boot parameters:


isolcpus=1,3  # Isolate CPUs 1 and 3 from general scheduling

Using cgroups v2

For modern systems with cgroups v2:


# Create new cgroup
mkdir /sys/fs/cgroup/cpuset/my_group

# Set allowed CPUs
echo 2 > /sys/fs/cgroup/cpuset/my_group/cpuset.cpus

# Add process
echo $PID > /sys/fs/cgroup/cpuset/my_group/cgroup.procs

When pinning processes:

  • Monitor cache hit rates with perf stat -e cache-misses
  • Check for load balancing issues using mpstat -P ALL 1
  • Consider NUMA architecture with numactl --hardware

Here's a complete Python wrapper using ctypes:


import ctypes
import os

libc = ctypes.CDLL('libc.so.6')
mask = (ctypes.c_ulong * 1)(1 << 0)  # CPU 0

if libc.sched_setaffinity(0, ctypes.sizeof(mask), mask) != 0:
    raise RuntimeError("Failed to set CPU affinity")