Home / Alt manpages / numactl(8)

  • numactl(8)
  • Admin command
  • linux

Pin a Linux Process to NUMA CPUs and Memory with numactl

A process spread across NUMA nodes can be quietly slower than one pinned close to its memory, and numactl is how you find out and fix it. You will inspect this host's NUMA layout, run a process on a chosen node, constrain its memory allocations, and verify the policy from inside the process itself. Examples use numactl 2.0.18 from Ubuntu package 2.0.18-1ubuntu0.24.04.1, as installed on this machine.

Allow about fifteen minutes. You need a shell and a NUMA-aware Linux kernel. The examples use /bin/true and numactl --show, so nothing here touches a service or persistent configuration. Only reach for elevated privileges if the command you are testing already requires them.

1. Check the installed command and topology

Start with read-only checks, no sudo needed:

$ command -v numactl
/usr/bin/numactl
$ numactl --version
numactl version 2.0.18
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1 2 3 4 5 6 7

Your output will differ. --hardware lists available NUMA nodes, the CPUs on each one, memory sizes and node distances. Record the node numbers before writing a policy. A single-node machine still accepts numactl policies, there is just no placement choice for them to make.

Checkpoint

Pick a node shown by --hardware. The examples below use node 0 because that is what exists on this host; swap in a real node number on your own machine.

2. See the current policy

Run --show in the shell you are using:

$ numactl --show
policy: default
preferred node: current
physcpubind: 0 1 2 3 4 5 6 7
cpubind: 0
nodebind: 0
membind: 0
preferred:

The exact CPU and node lists depend on the host and its cpuset. This describes the current process's policy, not a guarantee every child has unrestricted access: containers, service managers and parent processes can already narrow the allowed CPUs or nodes before numactl ever runs.

3. Bind a harmless process to CPUs and memory

Use --cpunodebind to select CPUs tied to a node and --membind to restrict allocation to it. The policy is inherited by the command's children:

$ numactl --cpunodebind=0 --membind=0 -- /bin/true
$ printf '%s\n' "$?"
0

A zero exit status only says /bin/true ran and returned zero, not that a large application's working set would fit on that node. With --membind, allocation is restricted to the listed nodes and can fail outright when they run short of memory. That is a deliberate failure mode, not an automatic fallback.

To watch the policy while a child is alive, launch a shell that prints its own view:

$ numactl --cpunodebind=0 --membind=0 -- /bin/sh -c 'numactl --show'
policy: bind
preferred node: current
physcpubind: 0 1 2 3 4 5 6 7
cpubind: 0
nodebind: 0
membind: 0

The CPU list can look wider than the node list because it reports physical CPU availability as well as the node binding. What matters here is nodebind: 0 and membind: 0.

4. Prefer memory without making it exclusive

--preferred names one preferred node but lets allocation fall back elsewhere if that node cannot satisfy it, a softer touch than --membind:

$ numactl --preferred=0 -- /bin/sh -c 'numactl --show'
policy: preferred
preferred node: 0
preferred: 0
  • --localalloc tries the process's current node first, then falls back elsewhere.
  • --interleave=0,1 spreads allocations round robin across at least two real nodes.

Do not copy a multi-node example onto a single-node host and expect it to provide any balancing; there is nothing to interleave across.

5. Keep command options in the right place

numactl parses its own options before it sees the command. Put -- before a child command whenever its options could be mistaken for numactl's:

$ numactl --cpunodebind=0 --membind=0 -- /bin/sh -c 'printf "child ok: %s\n" yes'
child ok: yes

numactl does not run the child through a shell. Pipes and redirections only work when you explicitly wrap the command in a shell yourself, as above. Quote that wrapper and its arguments carefully when the values come from outside the script.

6. Choose a policy that matches the failure boundary

For a database or worker with a measured memory-locality problem, start with --preferred or --localalloc. They keep fallback behaviour while making the intended locality visible. Move to --membind only once you have tested the application's memory demand and actually want an allocation failure rather than silent remote-node fallback.

--balancing turns on kernel NUMA balancing where supported, but the manpage says it should only be used alongside --membind. It is not a general performance switch: test it against the real workload and watch latency and CPU cost before it goes anywhere near a service unit.

Do not reach for sudo just because a policy failed. Check first that the node exists, the process's cpuset actually permits it, and the selected node has enough memory. When testing for a service, use the same account and environment the service runs under; a root test can hide an access or cpuset problem the real service will still hit.

7. Undo and verify the operational change

These command-line policies apply only to the launched process and its children, and they vanish when those processes exit, there is no persistent setting to undo in the examples above. To return a long-running service to its previous behaviour, stop it through its normal supervisor, remove the numactl wrapper or policy arguments from the service configuration, then start it through the supervisor again. Configuration changes like that may need elevated privileges and a maintenance window.

After starting the real command, verify from inside its process tree with numactl --show, or check the process's allowed CPUs and memory nodes through your usual host monitoring tools. Compare the result with what you intended, then measure the workload rather than treating a successful launch as a performance result on its own.

Done means

  • Recorded valid node numbers from numactl --hardware.
  • Know the difference between strict --membind and fallback-friendly --preferred or --localalloc.
  • Verified the child policy with numactl --show.
  • Used -- whenever child arguments could be confused with numactl options.
  • Have a measured rollback path before applying the wrapper to a real service.