Complete Mathematics of "Optimization 7: The Fused AddDot4x1 Micro-Kernel"¶
Optimization 7 fuses the four AddDot calls (Optimization 6) into a single loop over \(p\), keeping all four output accumulators alive together. This is the canonical, compute-optimal matvec micro-kernel: each pass over \(p\) reads one broadcast x_p and a contiguous 4-run of \(A\), and updates all four outputs at once.
1. The Mathematical Operation¶
For the 4×k panel of rows \(i..i+3\):
equivalently,
2. The Kernel Code¶
void AddDot4x1(int k, double *a, int lda, double *x, double *y){
for (p = 0; p < k; p++){
double x_p = x[p];
y[0] += A(0,p) * x_p; // y_i
y[1] += A(1,p) * x_p; // y_{i+1}
y[2] += A(2,p) * x_p; // y_{i+2}
y[3] += A(3,p) * x_p; // y_{i+3}
}
}
3. Why Fusing the Four Loops Helps¶
3.1 Compare Optimization 6 (four serial loops)¶
for p: y[0] += A(0,p)*x[p]
for p: y[1] += A(1,p)*x[p]
for p: y[2] += A(2,p)*x[p]
for p: y[3] += A(3,p)*x[p]
Four full traversals of p; x is walked four times; only one accumulator is live at a time.
3.2 Optimization 7 (single fused loop)¶
x[p] is loaded once per p and reused by all four rows. Four accumulators are live simultaneously → four independent dependency chains.
4. Instruction-Level Parallelism¶
The four updates share the same scalar x_p but accumulate into four different outputs, so they are independent:
Per p-step this is four independent FMAs with no chain between different rows. The FP units can execute them in parallel, and the compiler can load \(A(i:i+3,p)\) as one contiguous 4-vector while broadcasting x_p — the archetypal auto-vectorizable shape.
5. Memory Access: Contiguous Column Slices¶
Column-major layout makes the four \(A\) reads contiguous:
At each p-step we read a contiguous 4-double run (32 bytes — half a cache line), stepping by lda between columns. Compare this to the stride-lda single-element reads of the scalar row-dot versions: far better cache-line utilization and prefetching.
| Access | Behavior |
|---|---|
| \(x_p\) | 1 scalar load, broadcast to 4 rows |
| \(A(i:i+3,p)\) | 4 contiguous doubles |
| \(y[i:i+3]\) | 4 distinct output writes |
6. Data-Reuse / Traffic Summary per Kernel Call¶
For one \(4\times k\) panel:
| Quantity | Loads |
|---|---|
| \(x_p\) | \(k\) (each used 4×) |
| \(A\) elements | \(4k\) (each used once) |
| \(y\) updates | 4 (one each) |
\(x\) is reused 4× per element within the kernel — the only operand with reuse (consistent with matvec's memory-bound character, where \(A\) is streamed once).
7. FLOP Accounting¶
Per call: \(4\cdot 2k = 8k\) FLOPs; over \(m/4\) calls:
8. Summary¶
| Metric | Optim 6 | Optim 7 |
|---|---|---|
| Kernel | AddDot4x1 |
AddDot4x1 |
| Loops per group | 4 | 1 |
x traversal |
4 × | 1 × |
| Live accumulators | 1 | 4 |
| A read | strided rows | contiguous 4-run |
| FLOPs | \(2mk\) | \(2mk\) |
Optimization 7 is the fused, canonical 4-output matvec micro-kernel: it reads x once, spews a contiguous 4-vector of \(A\), and drives four independent accumulators — the structure that subsequent steps hold in registers (Optimization 8), pointer-advance (Optimization 9–11), and pack (Optimization 13–14).