Unlock CPU Performance: Master Hyper‑Threading and Affinity on Linux
This article explains hyper‑threading fundamentals, distinguishes physical, logical, and CPU cores, shows how to query CPU topology on Linux, introduces the concept of CPU affinity, demonstrates using the taskset command and related syscalls, and provides C API examples for setting process and thread affinity.
Preface
The article begins by introducing hyper‑threading (HT), a technology that uses special hardware instructions to present two logical cores for each physical core, allowing a single processor to execute multiple threads simultaneously.
Hyper‑Threading Overview
Increasing CPU clock speed or cache size faces diminishing returns, and many execution units remain under‑utilized due to memory bottlenecks and lack of instruction‑level parallelism (ILP). Hyper‑threading mitigates this by enabling thread‑level parallelism, reducing idle CPU time and improving overall efficiency.
CPU Core Terminology
CPU: the number of CPU chips installed on the motherboard.
Physical core: an actual processing core inside a CPU chip; a CPU may have multiple physical cores.
Logical core: a logical processor created by hyper‑threading, representing a software‑level thread.
The relationship between logical CPUs and physical resources is expressed as:
Logical CPU count = Physical CPU count × cores per CPU × 2 (if HT is enabled)Note: the multiplication assumes identical CPU models; otherwise, counts must be summed individually.
Linux Commands for CPU Information
# View number of physical CPUs
cat /proc/cpuinfo | grep "physical id" | sort -u | wc -l
# View cores per physical CPU
cat /proc/cpuinfo | grep "cpu cores" | uniq
# View total logical CPUs
cat /proc/cpuinfo | grep "processor" | wc -l
# View CPU model name
cat /proc/cpuinfo | grep "name" | cut -f2 -d: | uniqTo see which logical CPU a process runs on: ps -eo pid,args,psr To obtain a thread’s TID (Thread ID) you can use one of three methods: ps -efL | grep <em>program_name</em> List /proc/<pid>/task – directory names are TIDs. ps -To 'pid,lwp,psr,cmd' -p <PID> Getting the TID programmatically (C example):
#include <sys/syscall.h>
pid_t tid = syscall(__NR_gettid); // or syscall(SYS_gettid)CPU Affinity Concept
CPU affinity is a scheduler property that binds a process or thread to a specific set of CPUs. In SMP (Symmetric Multi‑Processing) systems, the Linux scheduler respects this binding, preventing the task from running on other CPUs. Natural affinity tries to keep a process on the same CPU to reduce migration overhead.
Affinity Representation
Affinity is expressed as a bitmask where each bit corresponds to a logical CPU; a set bit means the CPU is allowed. The least‑significant bit represents CPU 0. Hexadecimal notation is common, e.g.:
0x00000001 // CPU #0 only
0x00000003 // CPUs #0 and #1
0xFFFFFFFF // All CPUs (0‑31)Using the taskset Command
tasksetgets or sets a process’s CPU affinity. Basic syntax:
taskset [options] mask command [args...]
# or
taskset [options] -p [mask] pidKey options: -a, --all-tasks: apply the mask to all threads (TIDs) of the process. -p, --pid: operate on an existing PID instead of launching a new program. -c, --cpu-list: specify CPUs as a comma‑separated list (e.g., 0,5,7,9-11) instead of a bitmask.
Examples:
Run ls on CPU 0: taskset -c 0 ls -al /etc/init.d/ Show affinity of PID 1: taskset -p 1 Change the CPU affinity of a running top process to CPU 2: taskset -cp 2 <pid_of_top> Only the process owner (or root) can change affinity; any user can read it.
Programming API for Affinity
The underlying system calls are sched_setaffinity() and sched_getaffinity(). The GNU C library provides a set of macros for manipulating cpu_set_t objects.
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>
/* Manipulate CPU sets */
void CPU_ZERO(cpu_set_t *set);
void CPU_SET(int cpu, cpu_set_t *set);
void CPU_CLR(int cpu, cpu_set_t *set);
int CPU_ISSET(int cpu, const cpu_set_t *set);
int CPU_COUNT(const cpu_set_t *set);
/* Allocate dynamic CPU sets */
cpu_set_t *CPU_ALLOC(int num_cpus);
size_t CPU_ALLOC_SIZE(int num_cpus);
void CPU_FREE(cpu_set_t *set);
/* Set/Get affinity for a process (pid = 0 means calling process) */
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
/* Thread‑specific affinity */
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
int pthread_attr_getaffinity_np(const pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);Macros ending with _S operate on dynamically allocated sets (the extra setsize argument is obtained via CPU_ALLOC_SIZE()). The first two API functions affect processes; the remaining four affect threads. Passing pid = 0 targets the calling process.
References
The source code of taskset can be examined in the util‑linux package, e.g.,
ftp://ftp.kernel.org/pub/linux/utils/util-linux/vX.YZ/util-linux-X.YZ-xxx.tar.gzunder schedutils/taskset.c.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
