Register deprivation: spills and runtime under forced register scarcity
Register Deprivation: Spills and Runtime under Forced Register Scarcity
Environment: Intel Xeon E-2236 @ 3.40GHz | Compiler: gcc 15.2.0 | Date: 2026-07-13
This study examines the impact of artificial register pressure. By utilizing the gcc -ffixed-reg flag, the researchers progressively reserved registers to shrink the available pool, subsequently measuring the resulting spill counts and execution times across nine distinct kernels.
TL;DR: Restricting register availability slowed 8 out of 9 kernels by between and at the most constrained budget. SipHash was the sole exception, showing no performance degradation.
The source code is available at https://github.com/rjpower/spillbench, with raw data stored in results.json.
Key Findings
- Consistency: All results remained bit-identical regardless of the register budget; the reservation affected the instructions generated, not the mathematical outcome.
- Correlation: There is a weak correlation between the number of additional spill instructions and the resulting slowdown (Pearson across 105 data points).
- Variability: The "cost" of a single spill varies wildly—from (SipHash) to (FIR filter), a difference of roughly .
- Cross-File Impact:
- For SHA-256, reserving XMM registers increased runtime by , while reserving GP registers caused a hit.
- For Double Precision Matmul, reserving GP registers had no effect, but reserving XMM registers caused a slowdown.
- Note: SHA-256 is an integer kernel, but because
gccauto-vectorizes parts of it using SSE at-O2, XMM reservation is not inert (spills rose from ).
Experimental Setup
The researchers tested two primary hypotheses:
- H1: Reducing the register count will monotonically increase spills and decrease performance.
- H2: Performance degradation will only occur when the register file actually utilized by the kernel (GP for integer, XMM for float) is restricted.
Methodology
The team used gcc -ffixed-reg to remove registers from the allocator's pool for the entire compilation process. Each kernel was recompiled for every budget increment.
GP Register Removal Order:
r15 r14 r13 r12 rbp rbx r11 r10 r9 r8
(Reducing the pool from 15 allocatable registers down to 5; rsp is always excluded).
Constraints:
ABI registers (e.g., rax, rcx, rdx, rsi, rdi, and xmm0–3) were never reserved, as they are essential for argument passing and specific instructions like mul.
The Test Suite
Six kernels targeted the General Purpose (GP) file, and three targeted the XMM file.
| Kernel | File | Description | Validation Method |
|---|---|---|---|
| ChaCha20 | GP | 20-round stream cipher (1632-bit state) | RFC 8439 §2.3.2 |
| SHA-256 | GP | Compression function (8 vars + 64-word schedule) | SHA-256("abc") |
| SipHash-2-4 | GP | Keyed hash (464-bit ARX) | Reference vector |
| Integer matmul | GP | 44 register-blocked int64 micro-kernel | vs. naive triple loop |
| LZ77 compress | GP | Hash-chain match finder | Round-trip (comp decomp) |
| Quicksort | GP | In-place recursive int32 sort | Output verification |
| Double matmul | XMM | 44 register-blocked f64 micro-kernel | vs. naive triple loop |
| FIR filter | XMM | 16-tap filter (8 output lanes) | vs. naive tap sum |
| Mandelbrot | XMM | Escape-time (few live doubles per pixel) | Known escape counts |
Measurement Process
- Spill Metric: Static analysis via
objdumpcounting instructions referencing a fixed stack slot (e.g.,...(%rsp)), excluding array indexing. - Timing: Performed on a single pinned core.
- Hardware:
Intel Xeon E-2236 @ 3.40 GHz,gcc 15.2.0,-O2 -fPIC -shared. - Constraint: Hardware performance counters were unavailable (
perf_event_paranoid=4).
Result 1: Impact of Register Removal
Most kernels slowed down as registers were removed, though SipHash-2-4 remained unaffected.
Figure 2: Runtime trends for integer and float kernels.
Slowdown at Maximum Constraint:
| Kernel | File | Runtime (Full Min) | Slowdown | Spills (Full Min) |
|---|---|---|---|---|
| FIR filter | XMM | |||
| Integer matmul | GP | |||
| Double matmul | XMM | |||
| SHA-256 | GP | |||
| Quicksort | GP | |||
| ChaCha20 | GP | |||
| LZ77 compress | GP | |||
| Mandelbrot | XMM | |||
| SipHash-2-4 | GP |
Analysis of H1:
H1 is fully supported partially supported. While the general direction is correct (fewer registers slower code), the relationship is not strictly monotonic.
- Spill anomalies: ChaCha20 and Quicksort both saw static spill counts drop after a register was removed in certain steps.
- Runtime noise: Local dips occurred in LZ77 (5 steps), SipHash (3 steps), and Quicksort (3 steps), often within the measurement noise.
- Initial State: Integer matmul began with 60 spills even with a full register file because its block requires 16
int64accumulators, exceeding the 15 available GP registers.
Result 2: Spill Count as a Predictor
The number of added spills is a poor indicator of actual performance loss.
Correlation Analysis: Across all 105 test points, the Pearson correlation coefficient is .
Cost Per Added Spill:
| Kernel | Added Spills (Min) | Total Slowdown | Slowdown per Spill |
|---|---|---|---|
| FIR filter | |||
| Mandelbrot | |||
| Integer matmul | |||
| Double matmul | |||
| Quicksort | |||
| SHA-256 | |||
| ChaCha20 | |||
| LZ77 compress | |||
| SipHash-2-4 |
Observation: The disparity is stark: Integer matmul suffered a slowdown with only 22 extra spills, whereas LZ77 added 76 spills but only slowed by .
Example of a spill instruction identified by objdump:
movq %rax, -8(%rsp) # Store register to stack
movq -8(%rsp), %rdx # Reload from stack
The researchers conclude that the explanation for this variance likely lies in... [Article ends here]