Complete Mathematics of "Optimization 9: Pointer-Based Access to the A Panel"¶
Optimization 9 keeps the register-accumulated AddDot4x1 kernel but replaces the macro/index addressing of \(A\) with four pointers — one per row of the output tile. Each pointer walks down its column of \(A\) (in column-major layout) advancing by lda per \(p\)-step. The mathematics is unchanged; only the address arithmetic changes.
1. The Mathematical Operation (unchanged)¶
For \(r=0,1,2,3\):
2. The Kernel Code¶
double *a_0_ptr = &A(0,0);
double *a_1_ptr = &A(1,0);
double *a_2_ptr = &A(2,0);
double *a_3_ptr = &A(3,0);
for (p = 0; p < k; p++){
x_p = x[p];
c_0 += *a_0_ptr * x_p;
c_1 += *a_1_ptr * x_p;
c_2 += *a_2_ptr * x_p;
c_3 += *a_3_ptr * x_p;
a_0_ptr += lda; // next column
a_1_ptr += lda;
a_2_ptr += lda;
a_3_ptr += lda;
}
3. What the Pointers Point To¶
In column-major layout, \(A(i+r, 0) = a[0\cdot\text{lda}+i+r] = a[i+r]\). So the four pointers start at the four consecutive entries of column 0:
At step \(p\), after \(p\) increments of lda:
So *a_r_ptr yields exactly \(A_{i+r,p}\) — the row-\(r\) element of column \(p\). The four pointers stay one column apart, offset by \(1\) address, and advance together by lda each \(p\).
4. Address-Arithmetic Comparison¶
| Scheme | Address of \(A_{i+r,p}\) | Cost per access |
|---|---|---|
| Macro (Optim 8) | a[p*lda + (i+r)] |
integer mult by lda + add + load |
| Pointer (Optim 9) | *a_r_ptr (\(a_rPtr += lda\)) |
increment + load |
The multiply by lda moves out of the per-element path — the hot loop now only does four pointer increments and four loads per \(p\)-step. This frees integer ALU and load-address ports for the arithmetic.
5. Memory Pattern (unchanged physically)¶
Pointer arithmetic does not change the physical layout: the four pointers still jump by lda doubles each \(p\)-step (they are one column of \(A\) apart). So the cache-line footprint is the same as Optimization 7/8 — the four rows at offset \(- \text{lda}\) each step. What improves is the cost of computing the address, not the addresses themselves.
However, the four simultaneous streams (a_0Ptr..a_3Ptr all incrementing by the same lda) are a clean, predictable pattern that modern prefetchers track well — four parallel sequential-by-stride streams.
6. Summary¶
| Metric | Optim 8 | Optim 9 |
|---|---|---|
| Kernel | AddDot4x1 |
AddDot4x1 |
| FLOPs | \(2mk\) | \(2mk\) |
| A addressing | macro (p*lda) |
4 pointers, += lda |
| Accumulators | registers | registers |
| A-load sequence | strided column | strided column (same data) |
Optimization 9 removes the per-element index multiplication for \(A\), replacing it with four cheap pointer increments. The result is identical; the inner loop becomes a tight stream of loads + FMAs with minimal integer overhead — the immediate precursor to auto-vectorization (Optimization 11) and packing (Optimization 13–14).