/numa-programming
NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology,
$ npx -y skills add mohitmishra786/low-level-dev-skills --skill numa-programming --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/numa-programming
Context preview
The summary Claude sees to decide when to auto-load this skill.
NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology,
SKILL.md
numa-programming.SKILL.mdname: numa-programming
description: NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology, mbind, lstopo, or remote memory access.
NUMA Programming
Purpose
Guide agents through NUMA-aware programming: topology detection with `numactl` and sysfs, libnuma API (`numa_alloc_onnode`, `mbind`, `set_mempolicy`), process binding with `numactl`, NUMA-aware data structures, remote access diagnosis with `perf stat`, and `lstopo` visualization.
When to Use
- Multi-socket server shows poor scaling despite low CPU utilization
- Memory bandwidth saturation on one NUMA node
- Binding database or cache process to local memory
- Designing per-node freelists or sharded allocators
- Measuring remote vs local memory access latency
- Tuning HPC, DPDK, or custom allocator for socket locality
Workflow
1. Topology detection
# Hardware topology summary
numactl --hardware
# Detailed topology with distances
lstopo --of console
# sysfs nodes
ls /sys/devices/system/node/
cat /sys/devices/system/node/node0/meminfo
cat /sys/devices/system/node/node0/cpulist
Typical output:
available: 2 nodes (0-1)
node 0 cpus: 0-15
node 0 size: 65536 MB
node 1 cpus: 16-31
node 1 size: 65536 MB
node distances:
node 0 1
0: 10 21
1: 21 10
Distance 10 = local, higher = remote (cross-socket).
2. Process binding with numactl
# Bind to node 0 CPUs and memory
numactl --cpunodebind=0 --membind=0 ./myapp
# Interleave memory across all nodes
numactl --interleave=all ./myapp
# Preferred node (fallback if full)
numactl --preferred=0 ./myapp
# Show process NUMA policy
numactl --show
cat /proc/self/numa_maps
3. libnuma API
#include <numa.h>
#include <numaif.h>
#include <stdio.h>
int main(void) {
if (numa_available() < 0) {
fprintf(stderr, "NUMA not available\n");
return 1;
}
int node = numa_node_of_cpu(0);
printf("CPU 0 on node %d\n", node);
// Allocate on specific node
size_t size = 1024 * 1024 * 1024;
void *mem = numa_alloc_onnode(size, 0);
if (!mem) return 1;
// Bind existing memory
unsigned long nodemask = 1UL << 0;
mbind(mem, size, MPOL_BIND, &nodemask, sizeof(nodemask) * 8, 0);
numa_free(mem, size);
return 0;
}gcc -o numa_test numa_test.c -lnuma
| API | Purpose | |-----|---------| | `numa_alloc_onnode` | Allocate on specific node | | `numa_alloc_local` | Allocate on current CPU's node | | `mbind` | Set policy on existing mapping | | `set_mempolicy` | Default policy for subsequent allocs | | `move_pages` | Migrate pages to target node |
4. NUMA-aware data structures
// Per-node freelist — avoids cross-node synchronization
#define MAX_NODES 8
struct per_node_pool {
void *free_list[MAX_NODES];
int node_count;
};
void *pool_alloc_numa(struct per_node_pool *p) {
int node = numa_node_of_cpu(sched_getcpu());
void *blk = p->free_list[node];
if (blk) {
p->free_list[node] = *(void **)blk;
return blk;
}
return numa_alloc_onnode(BLOCK_SIZE, node);
}Pin threads to cores on the same node as their pool.
5. Thread affinity alignment
# Pin thread 0 to CPU 0 (node 0), allocate on node 0
numactl --cpunodebind=0 --membind=0 ./worker --id 0
numactl --cpunodebind=1 --membind=1 ./worker --id 1
#include <pthread.h>
#include <sched.h>
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(target_cpu, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
6. Remote access diagnosis
# Cache misses often spike with remote memory
perf stat -e cache-misses,cache-references,node-load-misses \
numactl --cpunodebind=0 --membind=1 ./myapp
# Compare local vs remote binding
perf stat numactl --cpunodebind=0 --membind=0 ./myapp
perf stat numactl --cpunodebind=0 --membind=1 ./myapp# NUMA hit/miss stats (if available)
perf stat -e node-loads,node-load-misses,node-stores ./myapp
7. Measuring remote penalty
// Microbenchmark: touch 1GB on local vs remote node
clock_t start = clock();
for (size_t i = 0; i < size; i += 4096)
sum += ((char *)mem)[i];Expect 1.5–3x slowdown for remote access depending on interconnect (QPI/UPI/Infinity Fabric).
8. lstopo visualization
# Graphical (if X11)
lstopo
# Text with memory/PCI
lstopo --of ascii
# Export for documentation
lstopo file.png
Shows: NUMA nodes, cores, caches, PCI devices — essential for DPDK NIC placement.
9. Decision tree
Poor scaling on multi-socket?
├── Check numactl --hardware
├── Verify thread and memory on same node
├── perf stat node-load-misses
├── Remote misses high?
│ ├── numactl --membind=local
│ └── Per-node data partitioning
└── Still slow → memory bandwidth bound; reduce sharing
Common Problems
| Symptom | Cause | Fix | |---------|-------|-----| | OOM on one node despite free RAM elsewhere | MPOL_BIND too strict | Use `--preferred` or interleave | | 2x slower after scaling threads | Remote memory access | `numactl --membind` matching CPU node | | Inconsistent benchmark results | OS migrated pages | `mbind` MPOL_BIND; mlock if needed | | DPDK NIC on wrong socket | PCI far from CPU | `lstopo`; bind EAL to local socket | | libnuma not found | Package not installed | `apt install libnuma-dev` | | First-touch policy surprise | Alloc on node 0, run on node 1 | Allocate from bound thread |
Related Skills
- `skills/allocators/custom-allocators` — per-node pool allocators
- `skills/async-io/dpdk` — `rte_malloc_socket`, NIC NUMA locality
- `skills/hpc/mpi` — MPI process binding per NUMA node
- `skills/profilers/hardware-counters` — cache miss measurement
- `skills/profilers/linux-perf` — perf NUMA events
- `skills/low-level
Read more
name: numa-programming description: NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology, mbind, lstopo, or remote memory access.
NUMA Programming
Purpose
Guide agents through NUMA-aware programming: topology detection with `numactl` and sysfs, libnuma API (`numa_alloc_onnode`, `mbind`, `set_mempolicy`), process binding with `numactl`, NUMA-aware data structures, remote access diagnosis with `perf stat`, and `lstopo` visualization.
When to Use
- Multi-socket server shows poor scaling despite low CPU utilization
- Memory bandwidth saturation on one NUMA node
- Binding database or cache process to local memory
- Designing per-node freelists or sharded allocators
- Measuring remote vs local memory access latency
- Tuning HPC, DPDK, or custom allocator for socket locality
Workflow
1. Topology detection
# Hardware topology summary numactl --hardware # Detailed topology with distances lstopo --of console # sysfs nodes ls /sys/devices/system/node/ cat /sys/devices/system/node/node0/meminfo cat /sys/devices/system/node/node0/cpulist
Typical output:
available: 2 nodes (0-1) node 0 cpus: 0-15 node 0 size: 65536 MB node 1 cpus: 16-31 node 1 size: 65536 MB node distances: node 0 1 0: 10 21 1: 21 10
Distance 10 = local, higher = remote (cross-socket).
2. Process binding with numactl
# Bind to node 0 CPUs and memory numactl --cpunodebind=0 --membind=0 ./myapp # Interleave memory across all nodes numactl --interleave=all ./myapp # Preferred node (fallback if full) numactl --preferred=0 ./myapp # Show process NUMA policy numactl --show cat /proc/self/numa_maps
3. libnuma API
#include <numa.h>
#include <numaif.h>
#include <stdio.h>
int main(void) {
if (numa_available() < 0) {
fprintf(stderr, "NUMA not available\n");
return 1;
}
int node = numa_node_of_cpu(0);
printf("CPU 0 on node %d\n", node);
// Allocate on specific node
size_t size = 1024 * 1024 * 1024;
void *mem = numa_alloc_onnode(size, 0);
if (!mem) return 1;
// Bind existing memory
unsigned long nodemask = 1UL << 0;
mbind(mem, size, MPOL_BIND, &nodemask, sizeof(nodemask) * 8, 0);
numa_free(mem, size);
return 0;
}gcc -o numa_test numa_test.c -lnuma
| API | Purpose | |-----|---------| | `numa_alloc_onnode` | Allocate on specific node | | `numa_alloc_local` | Allocate on current CPU's node | | `mbind` | Set policy on existing mapping | | `set_mempolicy` | Default policy for subsequent allocs | | `move_pages` | Migrate pages to target node |
4. NUMA-aware data structures
// Per-node freelist — avoids cross-node synchronization
#define MAX_NODES 8
struct per_node_pool {
void *free_list[MAX_NODES];
int node_count;
};
void *pool_alloc_numa(struct per_node_pool *p) {
int node = numa_node_of_cpu(sched_getcpu());
void *blk = p->free_list[node];
if (blk) {
p->free_list[node] = *(void **)blk;
return blk;
}
return numa_alloc_onnode(BLOCK_SIZE, node);
}Pin threads to cores on the same node as their pool.
5. Thread affinity alignment
# Pin thread 0 to CPU 0 (node 0), allocate on node 0 numactl --cpunodebind=0 --membind=0 ./worker --id 0 numactl --cpunodebind=1 --membind=1 ./worker --id 1
#include <pthread.h> #include <sched.h> cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(target_cpu, &cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
6. Remote access diagnosis
# Cache misses often spike with remote memory
perf stat -e cache-misses,cache-references,node-load-misses \
numactl --cpunodebind=0 --membind=1 ./myapp
# Compare local vs remote binding
perf stat numactl --cpunodebind=0 --membind=0 ./myapp
perf stat numactl --cpunodebind=0 --membind=1 ./myapp# NUMA hit/miss stats (if available) perf stat -e node-loads,node-load-misses,node-stores ./myapp
7. Measuring remote penalty
// Microbenchmark: touch 1GB on local vs remote node
clock_t start = clock();
for (size_t i = 0; i < size; i += 4096)
sum += ((char *)mem)[i];Expect 1.5–3x slowdown for remote access depending on interconnect (QPI/UPI/Infinity Fabric).
8. lstopo visualization
# Graphical (if X11) lstopo # Text with memory/PCI lstopo --of ascii # Export for documentation lstopo file.png
Shows: NUMA nodes, cores, caches, PCI devices — essential for DPDK NIC placement.
9. Decision tree
Poor scaling on multi-socket? ├── Check numactl --hardware ├── Verify thread and memory on same node ├── perf stat node-load-misses ├── Remote misses high? │ ├── numactl --membind=local │ └── Per-node data partitioning └── Still slow → memory bandwidth bound; reduce sharing
Common Problems
| Symptom | Cause | Fix | |---------|-------|-----| | OOM on one node despite free RAM elsewhere | MPOL_BIND too strict | Use `--preferred` or interleave | | 2x slower after scaling threads | Remote memory access | `numactl --membind` matching CPU node | | Inconsistent benchmark results | OS migrated pages | `mbind` MPOL_BIND; mlock if needed | | DPDK NIC on wrong socket | PCI far from CPU | `lstopo`; bind EAL to local socket | | libnuma not found | Package not installed | `apt install libnuma-dev` | | First-touch policy surprise | Alloc on node 0, run on node 1 | Allocate from bound thread |
Related Skills
- `skills/allocators/custom-allocators` — per-node pool allocators
- `skills/async-io/dpdk` — `rte_malloc_socket`, NIC NUMA locality
- `skills/hpc/mpi` — MPI process binding per NUMA node
- `skills/profilers/hardware-counters` — cache miss measurement
- `skills/profilers/linux-perf` — perf NUMA events
- `skills/low-level
A curated suite of AI agent skills for systems and low-level programming — C/C++, Rust, Zig, GPU, bare-metal firmware, Linux kernel/driver development, computer architecture, compiler internals, HPC, and more.
Repo: mohitmishra786/low-level-dev-skills
Other skills on low-level-dev-skills.
- /custom-allocators
Custom allocator skill for memory allocation strategies. Use when implementing pool/slab/arena allocators, tuning jemalloc/mimalloc, writing Rust GlobalAlloc, or benchmarking allocator performance. Activates on queries about jemalloc, mimalloc, tcmalloc, arena allocator,
Open skill - /af-xdp
AF_XDP skill for high-performance XDP sockets. Use when creating AF_XDP sockets, configuring UMEM and XSK rings, XDP_REDIRECT programs, copy vs zero-copy mode, or comparing with DPDK. Activates on queries about AF_XDP, xsk_umem, XDP_REDIRECT, libbpf xsk, or zero-copy XDP.
Open skill - /dpdk
DPDK skill for userspace packet I/O. Use when initializing EAL, configuring PMD drivers, using mbuf pools and rte_ring, setting up huge pages, RSS, or testpmd validation. Activates on queries about DPDK, EAL, rte_eth_rx_burst, hugepages, PMD, or testpmd.
Open skill - /io-uring
io_uring skill for Linux async I/O. Use when building high-performance servers with liburing, multi-shot operations, provided buffers, fixed files, zero-copy send, or tokio-uring. Activates on queries about io_uring, SQE/CQE, liburing, IORING_OP_PROVIDE_BUFFERS, or io_uring vs
Open skill - /adc-dac-baremetal
Bare-metal ADC and DAC skill. Use when configuring analog sampling, DMA-driven ADC, calibration, or DAC output on MCUs. Activates on queries about ADC bare-metal, sampling time, DMA ADC, or DAC channel setup.
Open skill - /baremetal-startup
Bare-metal startup skill for reset-to-main bring-up. Use when writing startup code, vector tables, .data/.bss init, stack setup, or crt0 for Cortex-M/RISC-V. Activates on queries about reset vector, VTOR, startup.s, bss init, or bare-metal entry point.
Open skill

