NumPy
Advanced 12 min read NumPy

Performance and Memory

Every millisecond the rest of your stack is borrowing

dtype, contiguous layouts, views vs copies, and the BLAS-backed operations decide whether a heavy computation takes seconds or stalls. Tuning numpy is tuning the whole data stack.

dtypememorystridealiasviewcopyprofilingnumexpr
Prerequisites: NumPy Arrays
Before we start

What you will learn

  • Classify dtype and memory costs
  • Convert hot loops to vectorized or numexpr forms
  • Profile and shrink allocations that dominate runtime
Introduction

What is this?

NumPy performance lives in its C arrays: choosing the right dtype sets memory; contiguous layouts keep cache-friendly data; operations composition avoids temporaries. numexpr accelerates chained expressions of large arrays; np.save/np.load or memmap handle persistence. Profiling (time, memory) finds the bottleneck before you optimize.

Motivation

Why do you need it?

  • Dtype choice alone moves memory 8x (float64→float32) with identical results at report precision.
  • Chained temporaries (a + b)*c allocate intermediates you never needed.
  • Cache misses dominate big-array performance more than algorithm fancy-ness.
  • Most 'slow pandas' stories are numpy-in-the-box problems.
Think about it

Mental model

Imagine

A freight depot: crate size, packing order, and road width

Think of

numpy performance

Smaller crates (dtype) fit more per truck (memory); tightly packed, in-order crates (contiguous) unload fast (cache); one wide road is faster than three zigzag lanes (vectorized). Tuning the depot — not re-training the driver — is where the wins are.

Mechanics

How it works

  • dtype downgrade: float32/int16 for storage when precision allows; money as cents ints.
  • Contiguous C-order (default) beats strided/F-order for element-wise ops.
  • Views (slices, reshape) avoid copies; explicit two-step ops create temporaries.
  • numexpr evaluates expressions (a + b*c) with fused, multithreaded kernels.
  • np.save binary storage beats CSV 10-100x serialize/reload.
  • Time with time.perf_counter, memory with sys.getsizeof or memory-profiler.

The performance lever set.

dtype
Layout
Allocation
Backend
Measure
Hands-on

Examples

Size the win before you code it

Size the win before you code it
import numpy as np, time
big = np.random.default_rng(1).normal(size=(2000, 2000)).astype(np.float32)
print(big.nbytes / 1e6, 'MB')
t0 = time.perf_counter()
out = (big * 2 + 1.0).sum()
print('%.3fs %.0f' % (time.perf_counter() - t0, out))
import numexpr as ne
t0 = time.perf_counter()
out2 = ne.evaluate("sum(big * 2 + 1.0)")
print('numexpr %.3fs %.0f' % (time.perf_counter() - t0, out2))

float32 halves memory, and numexpr fuses the multiply-add-sum into fewer passes for large arrays.

Real world

In production

  • A feature store stores float32 embeddings, trading tiny precision for big memory.
  • A daily scoring job evaluates chained expressions with numexpr, dropping runtime 30%+.
  • A pipeline persists intermediate arrays as .npy to avoid re-parsing huge CSVs.
Guidelines

Best practices

  • Profile before optimizing: time and memory first, then dtype → layout → backend.
  • Choose dtype at build time, not via astype after the fact.
  • Keep trivial arrays small for a moment's clarity before scaling.
  • Benchmark changes against a fixed seed and fixed workload.
Watch out

Common mistakes

  • Rounding everything to float64 because 'it's safe', paying 2-8x memory.
  • Chaining ops that generate temporaries in a hot loop.
  • Assuming a view shares memory and mutating the source by accident.
  • Optimizing compile order before measuring whether dtype was the issue.
When things break

Debugging tips

  • dtype OOM → step down bits after verifying value ranges.
  • Element-wise op mysteriously slow → check strides (ascontiguousarray).
  • Two processes fighting → memory true peak, not allocation sum; use memmap for huge arrays.
  • If numexpr and numpy disagree, they shouldn't for the same expression — verify broadcasting alignment first.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/2
1

Which change typically helps most on large numeric arrays?

2

Slicing a NumPy array returns a copy by default.

Go deeper

Official documentation