Table of Contents
numactl CPU Pinning
--physcpubind pins to specific logical CPUs (numbered 0, 1, 2, …) rather than entire NUMA nodes. Useful for fine-grained control when combined with thread affinity schemes (OpenMP, MPI).
numactl --physcpubind=0-3 --membind=0 ./app # cores 0-3, node 0 memory numactl --physcpubind=0,2,4,6 --membind=0 ./app # specific cores
--physcpubind=0-3 pins the process to logical CPUs 0, 1, 2, 3. The OS scheduler cannot move threads to cores 4-15 (assuming a 16-core machine). Memory still comes from the node you specify with --membind.
Logical vs physical cores: On CPUs with hyperthreading, each physical core has 2 logical cores. lscpu shows the mapping:
$ lscpu | grep -E "^Core|^Socket|^CPU\(" CPU(s): 16 Core(s) per socket: 8 Socket(s): 2
This shows 2 sockets, 8 cores per socket, 16 logical CPUs total (2 threads per core). Logical CPUs 0-7 map to physical cores 0-7 on socket 0; 8-15 map to cores 0-7 on socket 1.
Thread affinity integration: Combine --physcpubind with OpenMP's affinity:
export OMP_NUM_THREADS=4 export OMP_PLACES="{0,1,2,3}" export OMP_PROC_BIND=close numactl --physcpubind=0-3 --membind=0 ./app
OMP_PLACES tells OpenMP which cores to use; --physcpubind enforces it at the process level. Together, they guarantee each thread lands on the intended core.
Sparse core selection: Pin to non-contiguous cores if needed:
numactl --physcpubind=0,2,4,6 --membind=0 ./app
This pins to even-numbered cores only (skipping odd cores, perhaps to avoid hyperthreading overhead). Useful for avoiding interference between hyperthreads.
Verifying pinning:
numactl --physcpubind=0-3 --membind=0 ./app & PID=$! taskset -p -c $PID # show CPU affinity
taskset -p -c PID shows which CPUs the process is pinned to. Useful for confirming bindings took effect.
Performance tip: If you're pinning to specific cores, ensure your thread count matches. Pinning to 4 cores but creating 8 threads wastes the constraint—threads will contend and thrash.
