# numactl Binding **Binding** pins a process to specific NUMA nodes using `--cpunodebind` and `--membind`. Together, they guarantee locality: all threads run on one node, all memory allocates from the same node. ```bash numactl --cpunodebind=0 --membind=0 ./program # pin to node 0 numactl --cpunodebind=0-1 --membind=0-1 ./program # allow nodes 0 and 1 ``` `--cpunodebind=0` restricts the process to CPUs on node 0. The OS scheduler cannot move threads to other nodes. `--membind=0` restricts memory allocation to node 0's DIMM. Any `malloc` or memory fault-in allocates from node 0. **Single-node binding:** Most common for HPC. Bind a process to one node to maximize locality: ```bash numactl --cpunodebind=0 --membind=0 ./app ``` This is the strongest form of locality—every thread and every page are on the same node. Cross-socket accesses should be rare (only library code or OS operations). **Multi-node binding:** If a workload needs more memory than one node has: ```bash numactl --cpunodebind=0-1 --membind=0-1 ./app # allow 2 nodes ``` This allows threads on nodes 0 or 1, and memory can allocate from either. Locality is weaker (threads on node 0 may access memory on node 1), but you don't run out of memory. **Behavior when memory exhausted:** With `--membind=0`, if node 0 runs out of memory, the allocation fails (even if other nodes have free memory). For workloads of unknown size, use `--preferred` instead; see [[numactl-memory-policies]]. **Pinning with node lists:** ```bash numactl --cpunodebind=0,2 --membind=0,2 ./app # nodes 0 and 2 (skip 1) numactl --cpunodebind=0-3 --membind=0-3 ./app # nodes 0,1,2,3 (range) ``` Syntax: single node `0`, list `0,2`, or range `0-3`. Mixed syntax: `0-2,4` means nodes 0,1,2,4. **Starting already-running processes:** Binding must happen at process launch—before the first memory allocation—to be effective. You can't re-bind a running process and expect its existing memory to move: ```bash numactl --cpunodebind=0 --membind=0 ./app # correct: bind at start numactl --cpunodebind=0 ./already_running_app # wrong: app already allocated memory ``` The second command does nothing (app's memory stays where it was). **Interacting with fork/exec:** Binding is inherited by child processes spawned via `fork()` and `exec()`. The child inherits the parent's NUMA policy.