How NUMA Affects FFmpeg Encoding Performance

Non-Uniform Memory Access (NUMA) architecture significantly influences FFmpeg’s multi-threaded video encoding performance on multi-socket and high-core-count systems. This article explains how NUMA’s memory localization and latency imbalances affect FFmpeg, details the performance bottlenecks caused by cross-node memory access, and provides practical strategies to optimize multi-threaded encoding on NUMA-enabled hardware.

Understanding NUMA in Modern CPUs

In a NUMA architecture, system memory is divided into multiple zones (nodes), each directly attached to a specific CPU socket or processor die. While any CPU core can access any part of the system memory, a core accesses its “local” memory significantly faster than “remote” memory attached to another socket or die. Remote memory access must travel across interconnects like Intel’s UPI or AMD’s Infinity Fabric, which introduces latency and reduces available bandwidth.

How NUMA Impacts FFmpeg Encoding

Video encoding is highly memory-bandwidth intensive. When FFmpeg encodes a video using multi-threading, the impact of NUMA manifests in three main areas:

1. Inter-Node Memory Latency

By default, the operating system’s thread scheduler distributes FFmpeg threads across all available CPU cores to maximize utilization. If an FFmpeg process running on Socket 0 needs to access frame data cached in memory attached to Socket 1, it suffers from remote memory latency. As resolution and bitrate increase (e.g., 4K or 8K HEVC/AV1 encoding), this constant cross-node data transfer creates a severe memory bandwidth bottleneck, drastically reducing the encoding frames-per-second (FPS).

2. Cache Invalidation and Context Switching

If the operating system migrates an active FFmpeg thread from one NUMA node to another during an encoding job, the CPU cache associated with that thread is invalidated. The thread must reload its reference frames and lookup tables from the new node’s local memory or, worse, retrieve them from the original node’s memory over the interconnect. This frequent cache thrashing degrades performance.

3. Synchronization Overheads in Codecs

Many modern video codecs (such as libx264, libx265, and libsvtav1) rely heavily on thread synchronization for Wavefront Parallel Processing (WPP) and frame-level parallel encoding. When threads are scattered across different NUMA nodes, the synchronization primitives (like mutexes and barriers) must maintain cache coherency across physical sockets. This results in massive inter-socket communication overhead, negating the performance benefits of having extra CPU cores.

Mitigation and Optimization Strategies

To prevent NUMA-related performance degradation in FFmpeg, you can apply several optimization techniques:

NUMA Binding (Affinity)

In Linux, you can use the numactl utility to pin the FFmpeg process to a specific NUMA node and force it to allocate memory locally. This ensures that the threads and the memory they access reside on the same physical socket.

numactl --cpunodebind=0 --membind=0 ffmpeg -i input.mp4 -c:v libx265 -crf 28 output.mp4

Instance Parallelism (Split-Encoding)

Instead of running a single FFmpeg process across a 128-core dual-socket server—which yields diminishing returns due to NUMA bottlenecks—it is much more efficient to run multiple independent FFmpeg instances simultaneously. For example, on a two-socket system, you can run one FFmpeg instance pinned to Node 0 and another pinned to Node 1, processing different files or different segments of the same file.

Codec-Specific NUMA Awareness

Some encoders integrated into FFmpeg have built-in NUMA management. For example, the HEVC encoder libx265 features a --pools parameter (mapped via -x265-params pools=... in FFmpeg). By default, x265 attempts to detect NUMA nodes and create separate thread pools per node to minimize cross-socket communication. Manually configuring these pools can prevent threads from crossing NUMA boundaries unnecessarily.