How to Use the numactl Command in Linux to Control NUMA Memory Allocation and CPU Binding

Modern high-performance enterprise servers and multi-socket workstations employ Non-Uniform Memory Access (NUMA) architecture to scale memory throughput beyond the limits of a single unified bus. In a NUMA system, physical memory is partitioned across multiple local hardware nodes, each wired directly to specific CPU sockets or core complexes. While accessing memory attached locally to a processor’s socket is exceptionally fast, accessing memory wired to a remote socket introduces measurable interconnect bus latency and memory bandwidth contention.

For latency-critical enterprise workloads—including high-frequency trading engines, PostgreSQL/MySQL relational databases, in-memory caches (Redis/Memcached), and virtual machine hypervisors (KVM)—allowing the Linux kernel to assign memory and CPU cores haphazardly across NUMA boundaries degrades overall performance. The numactl command-line utility provides systems engineers with direct control over NUMA process scheduling policies, memory interleaving, and CPU node affinity.

Installing and Verifying numactl on Linux

The numactl package is available in the base repositories of all major Linux distributions:

To install on Debian and Ubuntu:

sudo apt update
sudo apt install numactl

To install on RHEL, AlmaLinux, Rocky Linux, or Fedora:

sudo dnf install numactl

To install on Arch Linux:

sudo pacman -S numactl

Inspecting Your System’s Hardware NUMA Topology

Before pinning processes or reallocating memory pools, execute numactl --hardware to view your physical server layout:

numactl --hardware

The output outputs three critical architectural indicators:

  • Available Nodes: The total number of independent NUMA nodes recognized by the kernel (e.g., available: 2 nodes (0-1)).
  • Node Core Mapping: The specific logical CPU IDs wired to each node (e.g., node 0 cpus: 0 1 2 3 8 9 10 11).
  • Node Distances (Latency Matrix): The relative memory access penalty between nodes. Local access is normalized at a distance of 10. A cross-node remote access distance of 21 indicates that retrieving data across the socket interconnect incurs more than double the latency of local RAM.

To view the current NUMA memory allocation policy governing your active terminal shell, run:

numactl --show

Understanding NUMA Memory Allocation Policies

When launching applications via numactl, you can select from four core memory allocation policies:

  • --membind=<nodes>: Strictly allocates memory from the specified nodes only. If memory on the selected nodes is exhausted, allocation fails rather than spilling over to remote nodes.
  • --preferred=<node>: Strongly prefers allocating memory from the specified node. If the preferred node runs out of available RAM, the kernel gracefully falls back to adjacent nodes without crashing the application.
  • --interleave=<nodes>: Round-robins memory page allocations across all specified nodes evenly. This policy is ideal for large shared datasets accessed equally by threads running across all sockets, maximizing aggregate memory bandwidth.
  • --localalloc: Directs the kernel to always allocate memory from whichever NUMA node the executing thread is currently running on.

How to Launch and Bind Processes with numactl

You can execute commands with specific CPU and memory bindings directly from the terminal:

1. Binding an Application to a Specific CPU Node and Local Memory

To launch a high-performance database daemon (such as MySQL or PostgreSQL) pinned exclusively to CPU cores and RAM on NUMA node 0:

numactl --cpunodebind=0 --membind=0 /usr/sbin/mysqld

This configuration ensures zero remote memory bus traversals, keeping cache hits high and access latency uniform.

2. Binding to Specific Logical CPU Cores

If you want to pin a process to a granular list of CPU cores (e.g., cores 0 through 3) while binding memory to node 0:

numactl --physcpubind=0-3 --membind=0 ./trading_engine_binary

3. Enabling Memory Interleaving for Large In-Memory Caches

For applications like Redis or high-throughput analytics pipelines where memory capacity requirements exceed a single socket’s RAM capacity:

numactl --interleave=all /usr/bin/redis-server /etc/redis/redis.conf

Interleaving spreads memory pages across all memory controllers simultaneously, preventing a single memory bus bottleneck.

Auditing Live NUMA Statistics and Memory Hits/Misses

To verify that your bindings are effectively preventing remote memory access penalties, monitor memory distribution using numastat:

numastat

Examine the key diagnostic metrics:

  • numa_hit: The number of memory allocations successfully served from the local node where the process thread was executing (higher is better).
  • numa_miss: Allocations that intended to be local but were forced to remote memory due to low local RAM headroom.
  • numa_foreign: Allocations intended for another node that spilled over onto this node.
  • local_node vs. other_node: Compares local memory hits against cross-bus remote hits. A high ratio of other_node traffic indicates poor process placement requiring numactl optimization.

To monitor a specific running process ID (PID) to confirm its current per-node memory footprint, run:

numastat -p <PID>

Leveraging numactl on Linux servers eliminates cross-socket latency bottlenecks, optimizing CPU cache locality and delivering predictable, deterministic throughput for demanding enterprise compute workloads.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.