GLSL Register Spilling: Hardware Causes and Performance

Register spilling in GLSL occurs when a shader requires more high-speed vector registers than the GPU hardware can physically allocate per thread, forcing the compiler to offload temporary variables into higher-latency memory spaces. This article examines the specific hardware constraints—including register file capacity, dynamic array indexing, and occupancy trade-offs—that cause register spilling, alongside the mechanisms through which it degrades rendering and compute pipeline throughput.

Hardware Conditions That Trigger Register Spilling

Modern GPUs execute GLSL shaders using thousands of concurrent threads grouped into warps (NVIDIA) or wavefronts (AMD). Each Execution Unit (EU), Streaming Multiprocessor (SM), or Compute Unit (CU) contains a fixed-size, on-chip register file. Register spilling is triggered when the dynamic requirements of a GLSL program exceed the capacity of this physical register pool.

1. Register File Exhaustion from Live Variable Pressure

The primary trigger is high register pressure. Each execution core divides its register file among active threads. When a GLSL shader maintains a large number of "live" variables simultaneously—common in physically based rendering (PBR), complex procedural noise, or unrolled loops—the compiler cannot map every variable to a dedicated physical register. Once the required register count per thread exceeds the maximum hardware limit per thread (often 255 to 256 32-bit registers), the compiler must spill the excess to local memory.

2. Dynamically Indexed Local Arrays

GPUs allocate registers based on static, compile-time variable names. If a GLSL shader declares a local array and accesses it using a dynamic, runtime-determined index (e.g., myArray[i] where i is a variable), most hardware architectures cannot resolve the register offset dynamically. Because physical register files lack dynamic runtime addressing within a single thread's allocation, the compiler is forced to spill the entire array into thread-local scratch memory.

3. Compiler Occupancy Heuristics

GPU compilers continuously balance register allocation against theoretical occupancy. If assigning more registers to an individual thread causes the total number of resident warps on a multiprocessor to drop below an architectural threshold, the compiler's optimization pass may intentionally choose to spill a subset of variables to memory. This is done to keep occupancy high enough to conceal instruction latencies, though it can backfire if the cost of the spill outweighs the benefit of extra warps.


How Register Spilling Degrades GLSL Performance

Despite its name, "local memory" in GPU terminology is not on-chip memory; it is private, per-thread memory located off-chip in global VRAM, accelerated only by L1 and L2 caches. Offloading register data to this space creates severe bottlenecks.

Latency Penalty

Physical registers have single-cycle access latency with virtually unlimited read/write bandwidth. Local memory access requires round-trips through the memory hierarchy. Even if a spilled variable hits the L1 cache, the operation takes tens of cycles. If the access misses L1 and falls to the L2 cache or device DRAM, latency climbs to hundreds of cycles (typically 200–800 cycles). A shader thread must stall during these memory operations unless it has independent instructions to execute.

Cache Contention and Thrashing

Every thread in an active warp executes the spill instruction concurrently. If a warp of 32 threads spills four 32-bit values, 128 bytes of data are simultaneously pushed into the memory subsystem. This heavy influx of thread-private data pollutes the L1/L2 data caches, evicting critical read-only data such as textures, uniform buffers, and shader storage blocks. This causes cache thrashing, degrading not just the spilling shader, but adjacent workloads sharing the same cache hierarchy.

Memory Bandwidth Saturation

When thousands of threads across multiple compute cores simultaneously spill to local memory, they consume substantial off-chip memory bandwidth. Because DRAM bandwidth is a strictly limited, shared resource, register spilling diverts bandwidth away from texture sampling, geometry fetching, and framebuffer write operations.

Destruction of Latency Hiding

GPUs rely on fine-grained multithreading to hide latency: while one warp waits for data, another executes. When register spilling occurs, threads spend more time waiting on memory, and the register pressure itself often forces the hardware to schedule fewer concurrent warps. The GPU loses its ability to swap to active threads, leading to low compute-unit utilization, pipeline bubbles, and a sharp drop in overall frames per second (FPS).