Skip to main content

7.7 Speeding up matrix computing

Source: Ideas from Computational Physics (Landau, Páez, Bordeianu), §7.7–7.7.2. Rewritten in original wording — not a paste of TuneNumpy.py or Fortran stride snippets. Continues 7.6b Hyperfine labs. Labs: 7.7b Matrix-speed labs.

Why loops feel slow​

A pure-Python for over list indices pays interpreter overhead on every element. The same arithmetic expressed as a vectorized array operation (NumPy ufuncs, or a comprehension that a library can accelerate) pushes work into compiled loops, contiguous memory, and often SIMD.

Even in this course's list-only sandbox you can practice the mindset: apply one formula to a whole vector, write SAXPY as a single pass, and notice how nested loops walk memory.

Timing an operation​

Wall-clock timing is the honest scoreboard. In Python:

import time

t0 = time.perf_counter()
# … work …
t1 = time.perf_counter()
print(t1 - t0) # seconds

Prefer perf_counter over time.time for short kernels. Run each candidate several times and compare medians — one noisy sample lies. Graded labs here lock correctness of kernels (same sum, same CC, same derivatives); you time stride variants on your own machine.

Stride and layout​

Matrices in memory are flat arrays with a stride (element gap) along each axis.

  • Row-major (C, default NumPy, Python list of rows): row ii is contiguous. Flat index for (i,j)(i,j) is roughly i⋅M+ji\cdot M+j on an N×MN\times M matrix — moving j→j+1j\to j+1 has stride 1; moving i→i+1i\to i+1 at fixed jj has stride MM.
  • Column-major (Fortran, classic MATLAB): columns are contiguous; the preferred inner loop flips.

Identical flop counts can differ in wall time by large factors when one nesting thrashs the cache and the other streams.

Loop A vs loop B (sum of squares)​

Both loops visit every entry of vec[N][M] and accumulate ∑ijvij2\sum_{ij} v_{ij}^{2}:

  • Large stride (bad on row-major): outer over columns jj, inner over rows ii — each inner step jumps by MM in the flat layout.
  • Unit stride (good on row-major): outer over rows ii, inner over columns jj — consecutive jj hit neighbors in memory.

Same arithmetic; different cache behavior. Labs compute both and check they agree.

Column vs row gathers​

Filling x[j] = m[0][j] walks a row (unit stride). Filling x[i] = m[i][0] walks a column (stride MM). Time both for large MM on your laptop — the row walk should win in CPython lists / NumPy C-order arrays.

Matrix multiply and array derivatives​

C=ABC=AB is O(N3)O(N^{3}) regardless of (i,j,k)(i,j,k) nesting; row-major layouts reward streaming rows of AA. Forward/central differences on a sampled y[i]y[i] are the same stride story. Pick the matmul sum, the flat-index/stride formulas, and the FD/CD stencils among lookalikes.

A low-stride nesting (row-major friendly) accumulates cijc_{ij} with aika_{ik} streaming along a row. A high-stride nesting that updates cijc_{ij} with an inner loop on ii at fixed j,kj,k jumps down columns. On a machine with NumPy, compare A @ B against a triple Python loop once A,BA,B hold ≳105\gtrsim 10^{5} floats. Labs lock FD/CD on yi=i2y_i=i^{2} at one interior point.

NumPy / linear-algebra cheat sheet (original short list)​

NeedTypical call / idea
Elementwise ff on an arrayf(a) ufunc or comprehension over a list
SAXPY y←ax+y\mathbf y\leftarrow a\mathbf x+\mathbf yLevel-1 BLAS building block
Matrix–vector AxA\mathbf xdot(A, x) / @
Matrix–matrix ABABdot(A, B) (not A*B)
Solve Ax=bA\mathbf x=\mathbf blinalg.solve(A, b)
Eigenpairslinalg.eig / eigh for symmetric

Use libraries in production; reimplement kernels in labs to see indexing and stride.

Quiz

Speeding up matrix computing

1 / 4

Vectorized array ops beat Python element loops mainly because…

Pick an answer to see if you are right.

Next: 7.7b Matrix-speed labs — maps, SAXPY, stride sums, both matmul orders, FD/CD. Then 7.8 Code-listing sketches.