Complete Mathematics of "Optimization 5: Pointer-Advanced x + 4× Reduction Unroll"¶
Optimization 5 refines the AddDot1x4 4-output kernel in two ways:
- It advances through
xwith a pointer (x_ptr) instead of an index — removing the index arithmetic for the contiguous vector. - It unrolls the \(p\)-loop by 4, processing four values of \(p\) per trip, which — combined with the four separate register accumulators introduced in Optimization 4 — exposes genuine instruction-level parallelism.
1. The Mathematical Operation (unchanged)¶
For \(r=0,1,2,3\):
2. The Kernel Structure¶
register double c_0, c_1, c_2, c_3, x_p;
double *x_ptr = x;
c_0 = c_1 = c_2 = c_3 = 0.0;
for (p = 0; p < k; p += 4){
x_p = *x_ptr; // x[p]
c_0 += A(0,p )*x_p; c_1 += A(1,p )*x_p;
c_2 += A(2,p )*x_p; c_3 += A(3,p )*x_p;
x_p = *(x_ptr+1); // x[p+1]
c_0 += A(0,p+1)*x_p; c_1 += A(1,p+1)*x_p;
c_2 += A(2,p+1)*x_p; c_3 += A(3,p+1)*x_p;
x_p = *(x_ptr+2); // x[p+2]
c_0 += A(0,p+2)*x_p; c_1 += A(1,p+2)*x_p;
c_2 += A(2,p+2)*x_p; c_3 += A(3,p+2)*x_p;
x_p = *(x_ptr+3); // x[p+3]
c_0 += A(0,p+3)*x_p; c_1 += A(1,p+3)*x_p;
c_2 += A(2,p+3)*x_p; c_3 += A(3,p+3)*x_p;
x_ptr += 4;
}
y0[0] += c_0; y1[0] += c_1; y2[0] += c_2; y3[0] += c_3;
3. Pointer Advancement for x¶
The four x reads use *x_ptr, *(x_ptr+1), *(x_ptr+2), *(x_ptr+3) and then x_ptr += 4. Because x is contiguous (stride 1), advancing by 4 is a simple +4 pointer increment each trip:
No index multiplication, no recomputed offsets — just one pointer bump per trip. (This addresses the vector side; the matrix \(A\) still uses the strided macro access A(r,p).)
4. ILP: Sixteen Independent FMAs per Trip¶
Each trip performs 16 multiply–adds across the four accumulators and four \(p\)-values:
The sixteen products \(A_{i+r,4t+q}\cdot x_{4t+q}\) are all independent (distinct \(A\) entries, distinct \(x\) entries), and they accumulate into four distinct chains \(c_0..c_3\). This gives both:
- Memory-level parallelism: the sixteen loads can be issued ahead (especially with software pipelining by the compiler);
- Four-way ILP in the accumulation chains.
Combined with pointer-based x, this is the tightest version so far of a scalar 4-output kernel.
5. Math: Grouped Partial Sums¶
Because the four contributions to each \(c_r\) in a trip are summed (by the hardware/compiler) before the chain advances, numerically:
Associativity of FP addition justifies regrouping; the sum is mathematically identical.
6. Memory Access Profile¶
| Access | Scheme | Behavior |
|---|---|---|
| \(x\) | pointerx_ptr |
contiguous, +4/trip |
| \(A(i:i+3, p)\) | macro strided (variable\(p\)) | 4-run per column, jumplda between columns |
| \(y\) | flush once | contiguous 4 writes |
7. Summary¶
| Metric | Optim 4 | Optim 5 |
|---|---|---|
| x addressing | indexx[p] |
pointer x_ptr |
| Reduction unroll | 1 | 4 |
| FMAs per trip | 4 | 16 |
| Independent chains | 4 | 4 (×4 terms each) |
| A access | strided | strided |
| FLOPs | \(2mk\) | \(2mk\) |
Optimization 5 does not change the mathematics — it tightens the inner loop by using a pointer for the contiguous x and unrolling the reduction 4×, so the compiler sees a wide batch of independent FMAs feeding four register accumulators. The remaining inefficiency is the stride-lda access to \(A\), which later levels address with pointers (Optimization 9+) and packing (Optimization 14).