Situation: In NumPy-heavy pipelines, large matrix ops dominate runtime — memory layout, contiguity, strides and broadcasting determine whether operations use fast vectorized kernels and caches or create expensive temporaries.Key concepts (brief):- C-order (row-major) stores rows contiguously; Fortran-order (column-major) stores columns contiguously. Strides tell how many bytes to step to get the next element along each axis.- A contiguous array (C or F) enables SIMD/BLAS kernels to stream memory efficiently. Non-contiguous views (e.g., many transposes, slicing with step) have surprising strides and often force copies when passed to low-level libraries.- Broadcasting avoids explicit expansion but can still create temporaries if the broadcast pattern cannot be expressed as a view for a downstream op.Refactoring suggestions to reduce copies & improve cache efficiency:1. Prefer contiguous layout that matches heavy kernels: - If you call BLAS-heavy ops on A (row-major loops) use C-order; for column-oriented routines or Fortran-ordered libraries use order='F'. - Use np.ascontiguousarray(A) or np.asfortranarray(A) to make a single copy early rather than repeated implicit copies.2. Favor views over copies: - Use slicing and reshape when it preserves contiguity. Check .flags['C_CONTIGUOUS'] / ['F_CONTIGUOUS'] and .strides. - Avoid .T on large arrays if it will trigger a copy later; explicitly choose storage order when creating arrays.3. Use in-place operations whenever safe: - a *= 2, np.add(a, b, out=a) avoids temporaries and reduces memory pressure. - For chained ops, use out= parameters (np.multiply, np.add) rather than a = a*b.4. Avoid broadcasting copies by aligning shapes: - Pre-reshape with views: b = b.reshape(1, -1) rather than b[None,:] if that yields contiguous strides. - If a scalar broadcast will be reused, materialize once with appropriate order: b = np.broadcast_to(b, shape).copy() only if needed repeatedly.5. Blocking / tiling for cache locality: - For very large matrix multiplies when you can’t rely on optimized BLAS, implement block-wise multiplication to fit L1/L2 cache:python
def blocked_matmul(A, B, block=256):
n = A.shape[0]
C = np.zeros((n,n), order='C')
for i in range(0,n,block):
for j in range(0,n,block):
for k in range(0,n,block):
C[i:i+block,j:j+block] += A[i:i+block,k:k+block] @ B[k:k+block,j:j+block]
return C
- Use block sizes tuned to CPU caches; ensure sub-blocks are contiguous to exploit streaming loads.6. Use specialized functions to avoid temporaries: - np.einsum can express complex contractions without intermediate allocations when optimized (use optimize=True). - np.dot/np.matmul call BLAS; ensure inputs are contiguous to avoid copies.Practical checklist:- Inspect .shape, .strides, .flags before heavy ops.- When transposing frequently for computation, create arrays in the needed order up front.- Replace chained expressions with in-place or out= variants.- Profile (line_profiler, memory_profiler) to find hidden copies.Trade-offs:- Forcing contiguity early costs a single allocation but prevents many repeated implicit copies — often a net win.- In-place ops save memory but mutate data; copy first if you need originals.This approach minimizes allocation churn, improves cache hits and lets BLAS/SIMD kernels run at native speed.