5.4.5 · D2Scientific Computing (Python)

Visual walkthrough — Vectorization — avoiding Python loops, speed comparison

1,947 words9 min readBack to topic

We will define every letter before we use it. Let's start with the very first thing: what does "time for a program" even look like as a picture?


Step 1 — What does "time to do a job" look like?

WHY draw it this way? Because a computer program that touches numbers has exactly this shape. If we can separate the "once" cost from the "per-number" cost, we can predict total time for any without running the code.

PICTURE. Below, the total time is a bar made of two pieces. The short dark stub on the left is the fixed setup. The long piece stretching right grows as we add more items — its length is (number of items) × (cost per item).

Figure — Vectorization — avoiding Python loops, speed comparison

Putting the picture into symbols — read it left to right, exactly as the bar is drawn:

Recall Why is this called a

linear model? Because if you double , the long piece doubles but the stub stays fixed — the total time is a straight line when plotted against (a line = "linear"). ::: Straight-line growth: slope , intercept .


Step 2 — The SAME job, two different workers

WHY? Because the whole story of vectorization is a story about . A Python loop, for each number, must: unbox the object, check its type, pick the right method, and allocate a fresh result object (see parent note). A vectorized call does one raw CPU instruction per number. So we give them different names:

PICTURE. Two bars stacked. The Python bar's per-item pieces are fat; the vectorized bar's are thin. Notice the vectorized bar has a bigger setup stub — calling into C has its own fixed cost — but its per-item pieces are so thin the total is far shorter.

Figure — Vectorization — avoiding Python loops, speed comparison

The two total times, term by term:


Step 3 — Building the speedup ratio

WHY a ratio and not a difference? Because "faster" is naturally a multiplier. Saying "5 seconds faster" is meaningless without a baseline; " faster" is scale-free and tells you the whole story regardless of array size.

PICTURE. Take the two bars from Step 2 and lay the Python bar's length over the vectorized bar's length. How many vectorized-bars fit inside one Python-bar? That count is the speedup.

Figure — Vectorization — avoiding Python loops, speed comparison

  • Numerator : the whole Python cost — grows with , slope .
  • Denominator : the vectorized cost — a fixed stub plus a thin slope.

This single fraction is the parent note's central result. The next steps ask: what does this fraction do as changes?


Step 4 — The tiny- case (where vectorization LOSES)

WHY this matters: People assume "vectorized is always faster." It is not. This step shows the degenerate case where the C-call setup is more expensive than just looping over 3 numbers in Python.

PICTURE. For , the vectorized bar is almost all setup stub — barely shorter than (or even longer than) the tiny Python bar. The speedup fraction dips below 1, meaning "slower."

Figure — Vectorization — avoiding Python loops, speed comparison

Set (the extreme degenerate case — an empty or near-empty array):

  • The numerator vanishes (no elements → no Python work saved).
  • The denominator stays at (you still pay to enter the C routine).
  • Result: speedup , i.e. all cost, no benefit. This is why the parent warns: don't vectorize 4-element arrays.

Step 5 — The huge- case (where vectorization WINS)

WHY the limit? We want the best possible speedup — the ceiling. A "limit as " is the mathematical tool for "what value does this approach as we keep growing?" It answers: what's the most we can ever hope for? We use a limit (not just a big number) because it gives a clean, exact ceiling independent of any particular .

PICTURE. As the bar grows rightward, the setup stub shrinks to a dot in comparison. The two per-item slopes ( vs ) are all that's left — and their ratio is the whole answer.

Figure — Vectorization — avoiding Python loops, speed comparison

Divide top and bottom of the fraction by (a legal move — it's multiplying by ), then send :

  • The term : a fixed number divided by something huge → shrinks to 0. This is the key move — setup washes out.
  • What survives: — the ratio of the two per-item costs.

With ns and ns:

That is where the famous "" comes from — it is not magic, it is exactly the ratio of how expensive one Python element-step is versus one C element-step.


Step 6 — The whole curve, and the break-even point

WHY find break-even? Because it answers the practical question: how big must my array be before vectorizing pays off? We find it by setting speedup (a tie) and solving for .

Set , cross-multiply, and collect :

  • = the break-even count. Below it, loop wins; above it, vectorize wins.
  • Numerator : the bigger the fixed setup, the larger the array you need to justify it.
  • Denominator : how much you save per element. Bigger savings → break even sooner.

Plug in ns, , :

So past about 10 elements vectorization starts to win here.

PICTURE. The full S-shaped rise: below 1 on the left (red danger zone), crossing at , flattening at the dashed ceiling .

Figure — Vectorization — avoiding Python loops, speed comparison

The one-picture summary

Figure — Vectorization — avoiding Python loops, speed comparison
Recall Feynman retelling — the whole walkthrough in plain words

Two workers do the same pile of sums. Python reads the full instruction sheet before touching each number — slow per number, but starts instantly. NumPy hires a fast C robot: the robot takes a moment to wake up (setup), then rips through the numbers almost for free. For a tiny pile the robot's wake-up time isn't worth it, so Python's head-start wins — that's why the speedup starts below 1. As the pile grows, the wake-up time becomes a rounding error, and all that's left is "how fast does each worker handle one number?" Python: ~100 ns. Robot: ~1 ns. Ratio: 100. So the speedup climbs and levels off at 100×. The exact size where the robot overtakes Python is the break-even = wake-up cost ÷ savings-per-number ≈ 10 items here. No magic — just a fat cost versus a thin cost, watched as the pile grows.


Connections

  • Vectorization — avoiding Python loops, speed comparison (Hinglish) — parent topic.
  • NumPy Arrays vs Python Lists — why is so small (packed typed memory).
  • Universal Functions (ufuncs) — the compiled C kernels that give .
  • Big-O Complexity — the linear model is per operation.
  • Broadcasting Rules — vectorizing multi-dimensional combinations without loops.
  • Numba and Cython — when even vectorization isn't enough, compile the loop directly.
  • Memory Layout and Cache — why huge- speedups flatten (memory bandwidth).