Sitemap
Dev Genius

Coding, Tutorials, News, UX, UI and much more related to development

Stop Using Python Loops

4 min readFeb 8, 2026

--

How I Achieved a 1000x Speedup with CuPy on Google Colab.

Press enter or click to view image in full size
Python Isn’t Slow. You’re Just Using It Wrong.
https://github.com/lovnishverma/Python-Getting-Started/blob/main/Comparative_Analysis_Acceleration_Libraries.ipynb

“Python is slow.” It’s the most common complaint developers make. But the truth is, Python isn’t slow — loops are slow.

If you are working in Data Science or AI, waiting for data preprocessing is a productivity killer. Today, I benchmarked a compute-heavy trigonometric operation on 10 million data points using four different approaches: Pure Python, NumPy, Numba, and CuPy.

Get Lovnish Verma’s stories in your inbox

Join Medium for free to get updates from this writer.

The results?
The GPU-accelerated method was 1,057 times faster than standard Python. Here is the code so you can reproduce it yourself.

The Experiment Setup

  • Hardware: Google Colab (Free Tier) with a T4 GPU.
  • Task: Calculate sin(x) * cos(x) for 10,000,000 elements.
  • Why this task? Trigonometry is “compute-bound,” forcing the processor to work hard, which makes it the perfect stress test.

Step 1: Configuration & Hardware Check

First, let’s set up the environment and ensure we have a GPU available.

import time
import math
import numpy as np
import cupy as cp
from numba import jit, prange
from tqdm import tqdm # Progress bar for the slow Python loop

# Hardware Check
gpu_count = cp.cuda.runtime.getDeviceCount()
print(f"Environment Ready.")
print(f"GPU Detected: {gpu_count}x NVIDIA T4")
# Configuration
N_SIZE = 10_000_000
print(f"Dataset Size: {N_SIZE:,} elements")

Output:

Environment Ready.
GPU Detected: 1x NVIDIA T4
Dataset Size: 10,000,000 elements

Round 1: The Baseline (Pure Python)

This is the standard for loop. Because of Python's Global Interpreter Lock (GIL) and dynamic typing, this method struggles significantly. I added a progress bar (tqdm) just to visualize the pain.

# 1. Pure Python Loop
def python_heavy_math(n):
result = 0.0
# TQDM adds a progress bar so we can see the slow execution speed
for x in tqdm(range(n), desc="Processing Python Loop"):
result += math.sin(x) * math.cos(x)
return result

print(f"Running Pure Python implementation on {N_SIZE:,} items...")
t0 = time.perf_counter()
python_heavy_math(N_SIZE)
t1 = time.perf_counter()
time_python = t1 - t0
print(f"\nPython Execution Time: {time_python:.4f} seconds")

Output:

Running Pure Python implementation on 10,000,000 items...
Processing Python Loop: 100%|██████████| 10000000/10000000 [00:03<00:00]
Python Execution Time: 3.1683 seconds

Verdict: 3.17 seconds. This is our baseline.

Round 2: NumPy (CPU Vectorization)

Next, we switch to NumPy. By vectorizing the operation, we push the loop execution down to optimized C code. This is the industry standard for a reason.

# 2. NumPy Implementation
print(f"Running NumPy implementation...")

# Create Data
data_np = np.arange(N_SIZE, dtype=np.float32)
t0 = time.perf_counter()
# Vectorized Operation
np.sum(np.sin(data_np) * np.cos(data_np))
t1 = time.perf_counter()
time_numpy = t1 - t0
print(f"NumPy Execution Time: {time_numpy:.4f} seconds")

Output:

Running NumPy implementation...
NumPy Execution Time: 0.2251 seconds

Verdict: 0.22 seconds. That is 14x faster than the Python loop. Good, but we can go faster.

Round 3: Numba (JIT Compilation)

Numba is a Just-In-Time (JIT) compiler. With a simple @jit decorator, it translates Python functions into optimized machine code that runs in parallel on the CPU.

# 3. Numba Implementation
@jit(nopython=True, parallel=True)
def numba_heavy_math(arr):
res = 0.0
for i in prange(len(arr)):
res += np.sin(arr[i]) * np.cos(arr[i])
return res

print(f"Running Numba implementation...")
# Warmup (Compilation)
numba_heavy_math(data_np)
# Benchmark
t0 = time.perf_counter()
numba_heavy_math(data_np)
t1 = time.perf_counter()
time_numba = t1 - t0
print(f"Numba Execution Time: {time_numba:.4f} seconds")

Output:

Running Numba implementation...
Numba Execution Time: 0.1056 seconds

Verdict: 0.10 seconds. Twice as fast as NumPy, just by adding a decorator!

Round 4: CuPy (The Nuclear Option)

Finally, we use CuPy, which is essentially NumPy for the GPU. We transfer the data to the video memory (VRAM) and calculate all 10 million points in parallel using thousands of CUDA cores.

# 4. CuPy Implementation
print(f"Running CuPy implementation...")
# Transfer data to GPU
data_cp = cp.arange(N_SIZE, dtype=cp.float32)
# Warmup
cp.sum(cp.sin(data_cp) * cp.cos(data_cp))
cp.cuda.Stream.null.synchronize()
# Benchmark
t0 = time.perf_counter()
cp.sum(cp.sin(data_cp) * cp.cos(data_cp))
cp.cuda.Stream.null.synchronize()
t1 = time.perf_counter()
time_cupy = t1 - t0
print(f"CuPy Execution Time: {time_cupy:.4f} seconds")

Output:

Running CuPy implementation...
CuPy Execution Time: 0.0030 seconds

Verdict: 0.003 seconds. This is instantaneous.

Final Results & Summary

Here is the final breakdown of the benchmark:

----------------------------------------
BENCHMARK RESULTS (Lower is Better)
----------------------------------------
1. Pure Python: 3.1683 s
2. NumPy (CPU): 0.2251 s
3. Numba (CPU): 0.1056 s
4. CuPy (GPU): 0.0030 s
----------------------------------------
SPEEDUP FACTOR:
CuPy is 75.1x faster than NumPy
CuPy is 1057.5x faster than Python

Key Takeaways

  • CuPy is 75x faster than NumPy: If you have a GPU, use it.
  • CuPy is 1000x faster than Python loops: Never write raw loops for math operations.
  • Numba is a great CPU alternative: If you don’t have a GPU, Numba still gave us a 2x speedup over NumPy.

The math didn’t change, only the library did. Stop using lists for heavy calculations!

For more tutorials like this, subscribe to my YouTube channel: Lovnish Verma

--

Dev Genius
Dev Genius

Published in Dev Genius

Coding, Tutorials, News, UX, UI and much more related to development