Module 6 · Lesson 2

Vectorization Performance Patterns

Three habits keep NumPy code fast:

  1. Never grow an array in a loop. Appending to a Python list and converting once at the end is fine; calling np.append/np.concatenate inside a loop reallocates the whole buffer every iteration.
  2. Preallocate when you must fill incrementally — np.empty(shape) or np.zeros(shape) once, then assign into slices.
  3. Reuse buffers with out= when you don’t need the original array anymore — np.add(a, b, out=a) writes the result into a’s existing memory instead of allocating a new array.
import time

n = 2_000_000
a = np.arange(n, dtype=np.float64)
b = np.ones(n)

start = time.perf_counter()
c = a + b                      # allocates a brand-new array
print(f"new allocation: {time.perf_counter() - start:.5f}s")

start = time.perf_counter()
np.add(a, b, out=a)            # reuses a's memory, no new allocation
print(f"in-place (out=): {time.perf_counter() - start:.5f}s")

Output:

new allocation: 0.00262s
in-place (out=): 0.00099s
2.1

Increment in place

Write increment_in_place(arr, amount) that adds amount to every element of arr in place (using the out= parameter so no new array is allocated) and returns arr itself.

💡 Hint

np.add(arr, amount, out=arr) writes the result back into arr and also returns arr.

✅ Solution
def increment_in_place(arr, amount):
    return np.add(arr, amount, out=arr)
2.2

Running totals, no loop

Write running_totals(values) that returns the cumulative sum of a 1-D array (result[i] = sum of values[0..i]) without writing a Python for or while loop.

💡 Hint

There's a single built-in NumPy function that does exactly this: np.cumsum.

✅ Solution
def running_totals(values):
    return np.cumsum(values)