Course contentsShow
← Rust
Lesson 1185 of 1,91010. Concurrency PrimitivesFree lesson

NUMA-Aware Memory Allocation

You will optimize data structures to respect non-uniform memory access hardware topologies.

In this lesson, you will learn how to optimize data structure placement to minimize latency in multi-socket systems by aligning memory access with physical CPU topology.

The Topology Challenge

In modern servers, Non-Uniform Memory Access (NUMA) architectures mean that your CPU has "local" memory (fast) and "remote" memory (slower, accessed via an interconnect like UPI or QPI). If your async tasks are running on a Work-Stealing Scheduler (Concept 24) but are frequently accessing memory allocated on a remote NUMA node, performance degrades significantly due to increased latency and interconnect contention.

Localizing Data Structures

To optimize for NUMA, we aim to ensure that the memory backing our data structures resides on the same NUMA node as the thread executing the task. While general-purpose allocators (like jemalloc or mimalloc) handle thread-caching, they don't always guarantee node-locality.

In high-performance Rust applications, we achieve NUMA awareness by pinning threads to specific cores and using node-local allocation pools. Imagine a per-node queue structure:

struct NodeLocalPool<T> {
    // Each node has its own buffer to ensure local memory access
    storage: Vec<Vec<T>>, 
}

impl<T> NodeLocalPool<T> {
    fn push_to_local_node(&mut self, node_id: usize, value: T) {
        // We ensure data is pushed to the buffer corresponding to the
        // node the current thread is pinned to.
        self.storage[node_id].push(value);
    }
}

Bridging to Async

When building a Thread-Per-Core architecture (Concept 23), you can leverage the fixed affinity of your threads. By knowing which node_id your executor is currently running on, you can restrict your memory allocations to that specific domain. This prevents the "remote access penalty," where a CPU core must wait for data to travel across the interconnect, effectively stalled by the memory controller on a different socket.

When profiling async task contention (Concept 21), look for high cache-miss rates or interconnect traffic—these are often signals that your task state is being migrated away from the memory it frequently touches. By pinning tasks and localizing their state buffers, you ensure the memory controller and the CPU core are always in perfect sync.

Key Takeaway: To maximize throughput in high-concurrency systems, ensure that data structures used by specific async executors are allocated within the same physical memory domain as the threads executing them.