5.4.5Scientific Computing (Python)

Vectorization — avoiding Python loops, speed comparison

1,911 words9 min readdifficulty · medium

WHY does this matter?

WHY is the Python loop slow? Each iteration of a Python loop must:

  1. Fetch the next object (a boxed PyObject, not a raw number).
  2. Check its type at runtime (Python is dynamically typed).
  3. Dispatch to the correct __add__/__mul__ method.
  4. Allocate a new Python object for the result.

That is dozens of machine instructions to add two numbers. A NumPy array stores raw float64 values packed tightly in memory, so the C loop just does result[i] = a[i] + b[i] — one CPU instruction, no boxing, no type checks.


WHAT is the speed model? (Derivation from scratch)

Let's derive the expected speedup instead of memorizing "100×".

Total time for an operation on NN elements:

T=csetup+Ncper-elementT = c_{\text{setup}} + N \cdot c_{\text{per-element}}

  • For a Python loop: cper-elementpyc^{\text{py}}_{\text{per-element}} is large (boxing, type check, dispatch). Call it τpy50200\tau_{\text{py}} \approx 50\text{–}200 ns.
  • For a vectorized op: cper-elementvecc^{\text{vec}}_{\text{per-element}} is just a raw CPU op. Call it τvec0.52\tau_{\text{vec}} \approx 0.5\text{–}2 ns. There is a fixed C-call setup csetupc_{\text{setup}}.
Figure — Vectorization — avoiding Python loops, speed comparison

HOW do I vectorize? (Patterns)


Common mistakes (Steel-manned)


Forecast-then-Verify


Recall Feynman: explain to a 12-year-old

Imagine you must stamp 1,000,000 envelopes. Way 1 (Python loop): for each envelope you walk to the cupboard, take out the stamp, read the instructions, stamp it, put the stamp back, write a note. Repeat a million times — exhausting! Way 2 (vectorization): you tell a fast robot "stamp ALL of these the same way," and it does the whole pile in one smooth go without re-reading instructions each time. Same result, but the robot doesn't waste time on the boring setup a million times. That's why vectorized code is so much faster — it does the repetitive thinking once, not once per item.


Flashcards

Why is a Python for loop over numbers slow compared to NumPy?
Each iteration boxes objects, does runtime type-checking, method dispatch, and new-object allocation — dozens of instructions per element instead of one CPU op.
What does "vectorization" mean?
Expressing an operation on a whole array at once so the looping runs inside compiled C, not the Python interpreter.
Give the time model for an array op.
T=csetup+Nτper-elementT = c_{\text{setup}} + N\,\tau_{\text{per-element}}; speedup τpy/τvec\to \tau_{py}/\tau_{vec} for large NN.
For very small arrays, is vectorization always faster?
No — the fixed C-call setup csetupc_{\text{setup}} dominates, so a scalar/loop can be faster.
Why is np.append inside a loop bad?
It copies the entire array each call → O(N2)O(N^2). Build a list then np.array, or pre-allocate np.empty.
Does np.vectorize(f) make f fast?
No — it's essentially a Python-level loop; use NumPy ufuncs or numba for real speed.
How do you vectorize a per-element if (e.g. ReLU)?
Use a boolean mask: a[a<0]=0 or np.where(a<0,0,a).
What is broadcasting used for?
Combining arrays of different shapes (e.g. row[:,None]+col[None,:]) to avoid nested Python loops.
Why can iterating for x in np_array be slower than a list?
Each element is unboxed into a np.float64 object, adding overhead beyond a normal Python loop.
What often limits vectorized speed at large N?
Memory bandwidth — the CPU waits on data, not on arithmetic.

Connections

  • NumPy Arrays vs Python Lists — contiguous typed memory is why vectorization works.
  • Broadcasting Rules — vectorizing multi-dimensional combinations.
  • Universal Functions (ufuncs) — the compiled C kernels you call.
  • Big-O Complexity — why np.append-in-loop is O(N2)O(N^2).
  • Numba and Cython — when even vectorization isn't enough.
  • Memory Layout and Cache — bandwidth limits on vectorized ops.

Concept Map

incurs

from

pushes loop into

operates on

avoids

replaces

gives

large N

tiny N setup dominates

large tau_py in

Python for loop

Per-element overhead

Boxing type check dispatch

Vectorization

Compiled C loop

Raw float64 packed memory

Time model T = setup + N x tau

Speedup ratio

Limit = tau_py / tau_vec ~ 100x

Small N vectorization slower

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, Python ka for loop slow kyun hota hai? Kyunki har ek number ke liye Python ko bahut kaam karna padta hai — type check karo, object box karo, sahi __add__ method dhoondho, aur naya object banao. Yeh sab ek hi addition ke liye! Agar tumhare paas 10 lakh numbers hain, toh yeh boring kaam 10 lakh baar repeat hota hai. Vectorization ka matlab hai: poore array pe ek saath operation karo (jaise a + b), aur asli looping NumPy ke pre-compiled C code ke andar chale — jaha boxing aur type-check ka jhanjhat nahi hota.

Speed model simple hai: T=csetup+N×τT = c_{setup} + N \times \tau. Python loop me τ\tau bada (~100 ns), vectorized me chhota (~1 ns). Isiliye bade arrays pe speedup ~100× tak jaata hai. Lekin chhote arrays pe woh fixed c_setup (C-call ka overhead) dominate kar deta hai, toh kabhi-kabhi vectorize karna ulta slow bhi ho sakta hai. Yahi reason hai ki hamesha %timeit se measure karo.

Ek important galti: loop ke andar np.append mat karo — woh har baar pura array copy karta hai, O(N2)O(N^2) ho jaata hai, list se bhi bura! Behtar: Python list me append karo, phir np.array(list) se convert karo, ya np.empty(N) se pehle se jagah bana lo. Aur ek aur trap — np.vectorize(f) naam se lagta hai fast hoga, par andar se woh bhi Python loop hi hai, koi compilation nahi. Asli speed ke liye NumPy ke ufuncs (jaise np.where, data**2, .sum()) use karo, ya numba laga lo.

Yaad rakhne ka mantra: "Loop in C, not in Me." Agar tum khud loop chala rahe ho Python me, toh slow; NumPy ko poore array de do taaki woh C me loop chalaaye — phir code rocket ban jaayega.

Go deeper — visual, from zero

Test yourself — Scientific Computing (Python)

Connections