BLAS & LAPACK Scientific¶
When you'd use this
The linear algebra engines that make NumPy fast.
Understand the optimized linear-algebra libraries under NumPy/SciPy so you can get maximum numeric performance.
What you'll learn¶
- What BLAS and LAPACK are
- Why they make NumPy fast
- The three BLAS levels
- Naive vs library matrix multiply (tested naive version)
- How this shapes performance
Behind NumPy's speed sit two venerable Fortran/C libraries: BLAS and LAPACK. Understanding them explains why A @ B in NumPy is thousands of times faster than a Python loop.
What they are¶
The low-level linear-algebra libraries (matrix multiply, solvers) that NumPy/SciPy call under the hood.
- BLAS (Basic Linear Algebra Subprograms) — low-level routines for vector and matrix operations: dot products, matrix-vector, matrix-matrix multiply. Decades of hand-tuned optimization.
- LAPACK (Linear Algebra PACKage) — higher-level routines built on BLAS: solving linear systems, eigenvalues, decompositions (LU, QR, SVD).
NumPy / SciPy (Python API)
│ calls
▼
LAPACK (solvers, decompositions)
│ built on
▼
BLAS (multiply, dot — hand-tuned assembly)
│
▼
CPU (SIMD vector instructions, cache-optimized, multithreaded)
When you call numpy.linalg.solve or A @ B, you're really invoking optimized BLAS/LAPACK code — often an implementation like OpenBLAS, Intel MKL, or Apple Accelerate, tuned for your exact CPU.
Why it's fast: naive vs optimized¶
Tuned BLAS beats a naive triple loop by orders of magnitude through cache blocking and SIMD.
Here's a naive matrix multiply in pure Python — correct but slow. Runnable:
def matmul(A, B):
n, m, p = len(A), len(B), len(B[0])
result = [[0] * p for _ in range(n)]
for i in range(n):
for k in range(m):
aik = A[i][k]
for j in range(p):
result[i][j] += aik * B[k][j]
return result
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
print(matmul(A, B))
Output:
The math is right ([[1·5+2·7, 1·6+2·8], ...]), but this triple loop is orders of magnitude slower than BLAS for large matrices. Why BLAS crushes it:
- SIMD — one CPU instruction multiplies several numbers at once.
- Cache blocking — data is processed in chunks that fit in fast CPU cache, avoiding slow memory trips.
- Multithreading — work spread across cores.
- Hand-tuned assembly — decades of optimization for specific chips.
You will never beat BLAS with Python loops. The lesson: express math as array operations so NumPy dispatches to BLAS, rather than looping in Python (see Vectorization).
The three BLAS levels¶
Vector, matrix-vector, and matrix-matrix operations — level 3 is where tuned libraries shine.
BLAS routines are grouped by how much work they do per unit of data — which determines how well they use the hardware:
| Level | Operation | Example | Performance |
|---|---|---|---|
| 1 | vector-vector | dot product, y = ax + y | Memory-bound (little reuse) |
| 2 | matrix-vector | y = Ax | Moderate |
| 3 | matrix-matrix | C = AB | Compute-bound — best hardware use |
Level 3 (matrix-matrix) is the sweet spot: it does O(n³) work on O(n²) data, so each value loaded from memory gets reused many times, keeping the CPU busy. This is why algorithms are often reformulated to use matrix-matrix products — it's the operation hardware runs most efficiently.
LAPACK: the higher-level solvers¶
Decompositions and linear-system solvers built on BLAS — what NumPy calls under the hood.
LAPACK provides what you actually call for real problems:
import numpy as np # pip install numpy
A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = np.linalg.solve(A, b) # solve Ax = b (LAPACK under the hood)
eigenvalues = np.linalg.eigvals(A) # eigenvalues (LAPACK)
U, S, Vt = np.linalg.svd(A) # singular value decomposition (LAPACK)
NumPy snippet follows documented API
NumPy isn't installed here, so this isn't run-verified (the naive matmul is). Each of these calls dispatches to LAPACK routines that are numerically careful and fast — reimplementing an SVD or a stable linear solver by hand is a research project in itself. Use the library.
Practice exercises¶
- Time the naive
matmulon growing matrix sizes and observe the O(n³) growth. - Explain why a Level-3 (matrix-matrix) operation uses hardware better than Level-1 (vector).
- Describe what happens under the hood when you write
A @ Bin NumPy. - Look up which BLAS implementation your NumPy uses (
np.show_config()) — MKL, OpenBLAS, etc. - Explain why "vectorize, don't loop" is the golden rule of NumPy performance.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.