Why custom ufuncs matter in NumPy - Performance Analysis
We want to see how using custom ufuncs affects the time it takes to run numpy operations.
How does the speed change when we replace normal Python loops with custom ufuncs?
Analyze the time complexity of the following code snippet.
import numpy as np
def square(x):
return x * x
vectorized_square = np.frompyfunc(square, 1, 1)
arr = np.arange(1_000_000)
result = vectorized_square(arr)
This code creates a custom ufunc to square each element in a large array.
Look at what repeats in this code.
- Primary operation: Applying the square function to each element.
- How many times: Once for every element in the array (1,000,000 times).
As the array gets bigger, the time to run grows in a clear way.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 |
| 100 | 100 |
| 1000 | 1000 |
Pattern observation: The number of operations grows directly with the number of elements.
Time Complexity: O(n)
This means the time to complete the operation grows in a straight line with the input size.
[X] Wrong: "Custom ufuncs make the operation run in constant time regardless of input size."
[OK] Correct: Even with custom ufuncs, each element must be processed, so time grows with input size.
Understanding how custom ufuncs scale helps you explain performance improvements clearly and confidently.
"What if we used a built-in numpy ufunc instead of a custom one? How would the time complexity change?"