Skip to content

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\):

\[ y_{i+r} \;\mathrel{+}=\; \sum_{p=0}^{k-1} A_{i+r,\,p}\,x_p. \]

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:

\[ a_0Ptr \to A(i,0),\quad a_1Ptr \to A(i+1,0),\quad a_2Ptr \to A(i+2,0),\quad a_3Ptr \to A(i+3,0). \]

At step \(p\), after \(p\) increments of lda:

\[ a_rPtr \;\to\; a[i+r + p\cdot\text{lda}] \;=\; A(i+r,\;p). \]

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).