CPU Topology#
Join provides a CPU topology detector that reads hardware structure at runtime from the Linux sysfs interface.
CpuTopology is a singleton that exposes the physical layout of the machine — sockets, cores, hardware threads, and NUMA nodes — and is used internally by ThreadPool and distribute() for topology-aware thread sizing.
Concepts#
Logical CPU#
A logical CPU (also called a hardware thread) is what the OS schedules work on. On a system with Hyper-Threading or SMT enabled, each physical core exposes two or more logical CPUs.
LogicalCpu
├── id — OS CPU index (0, 1, 2, ...)
├── core — physical core ID
├── socket — physical socket ID
└── numa — NUMA node IDPhysical core#
A physical core groups all the logical CPUs that share its execution units.
PhysicalCore
├── id — physical core ID
├── socket — physical socket ID
├── numa — NUMA node ID
└── threads — list of logical CPUs (SMT/HT threads)NUMA node#
A NUMA node groups the physical cores that share the same memory controller. On single-socket systems there is typically one node (node 0).
NumaNode
├── id — NUMA node ID
└── cores — list of physical core IDs belonging to this nodeAccessing the topology#
CpuTopology is a singleton — construction and detection happen once on first access.
#include <join/cpu.hpp>
using namespace join;
const CpuTopology* topo = CpuTopology::instance();CpuTopology is neither copyable nor movable.
Physical cores#
const auto& cores = topo->cores();
for (const auto& core : cores)
{
// core.id — physical core ID
// core.socket — socket ID
// core.numa — NUMA node ID
// core.threads — logical CPUs on this core
}Cores are sorted by socket first, then by core ID within each socket.
Primary thread#
To get the first logical CPU of a core (avoids relying on a hyper-thread):
int cpuId = core.primaryThread(); // -1 if no threads
This is the recommended way to pin a thread to a core without activating a sibling HT thread.
NUMA nodes#
const auto& nodes = topo->nodes();
for (const auto& node : nodes)
{
// node.id — NUMA node ID
// node.cores — physical core IDs in this node
}Nodes are sorted by node ID.
Common queries#
Number of physical cores#
size_t coreCount = topo->cores().size();This is the value used by ThreadPool and distribute() as the default thread count.
Number of logical CPUs (with HT)#
size_t logicalCount = 0;
for (const auto& core : topo->cores())
{
logicalCount += core.threads.size();
}Cores on a specific NUMA node#
int targetNode = 0;
for (const auto& core : topo->cores())
{
if (core.numa == targetNode)
{
// use core.primaryThread() to pin a thread here
}
}Cores on a specific socket#
int targetSocket = 0;
for (const auto& core : topo->cores())
{
if (core.socket == targetSocket)
{
// ...
}
}Integration with Thread#
CpuTopology is typically combined with Thread::affinity() to pin threads to specific cores:
const auto& cores = CpuTopology::instance()->cores();
Thread thread([]() {
// work
});
// pin to the primary hardware thread of core 0
thread.affinity(cores[0].primaryThread());Integration with ThreadPool and distribute()#
Both ThreadPool and distribute() use CpuTopology to size their worker count:
// ThreadPool default = number of physical cores
ThreadPool pool; // pool.size() == topo->cores().size()
// distribute() uses the same count, capped to element count
distribute(data.begin(), data.end(), [](auto first, auto last) {
// ...
});Debug dump#
When compiled with DEBUG defined, the full topology can be printed to standard output:
#ifdef DEBUG
CpuTopology::instance()->dump();
#endifExample output on a dual-core HT system:
NUMA 0:
Socket 0:
Core 0: [ 0, 4 ]
Core 1: [ 1, 5 ]
Core 2: [ 2, 6 ]
Core 3: [ 3, 7 ]Each bracketed list shows the logical CPU IDs for that core.
The first ID in each list is the one returned by primaryThread().
Implementation notes#
Detection reads from /sys/devices/system/cpu/cpuN/topology/ and scans for nodeN symlinks to determine NUMA membership.
If sysfs is unavailable or a value cannot be read, -1 is returned for that field.
The result is a stable, sorted view of the hardware that does not change after the singleton is constructed.
Summary#
| Feature | Supported |
|---|---|
| Physical core enumeration | ✅ |
| Logical CPU enumeration | ✅ |
| SMT / HT awareness | ✅ |
| NUMA node detection | ✅ |
| Multi-socket support | ✅ |
| Primary thread selection | ✅ |
| Singleton lifecycle | ✅ |
| Runtime sysfs detection | ✅ |
| Debug dump | ✅ (DEBUG) |