This commit is contained in:
illustris
2026-01-08 18:11:30 +05:30
commit 4fb1bd90db
32 changed files with 3058 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# Scenario 1: Python to C Optimization
## Learning Objectives
- Use `time` to measure execution time
- Profile Python code with `cProfile`
- Generate flamegraphs with `py-spy`
- Use `ctypes` to call C code from Python
## Files
- `prime_slow.py` - Pure Python implementation (slow)
- `prime.c` - C implementation of the hot function
- `prime_fast.py` - Python calling C via ctypes
## Exercises
### Step 1: Measure the baseline
```bash
time python3 prime_slow.py 100000
```
Note the `real` time (wall clock) and `user` time (CPU time in user space).
### Step 2: Profile with cProfile
```bash
python3 -m cProfile -s cumtime prime_slow.py 100000
```
Look for:
- Which function has the highest `cumtime` (cumulative time)?
- How many times (`ncalls`) is `is_prime` called?
### Step 3: Generate a flamegraph
```bash
py-spy record -o prime_slow.svg -- python3 prime_slow.py
```
Open `prime_slow.svg` in a browser. The widest bar at the top shows where time is spent.
### Step 4: Compile and run the optimized version
```bash
# Compile the C library
gcc -O2 -fPIC -shared -o libprime.so prime.c
# Run the fast version
time python3 prime_fast.py 100000
```
### Step 5: Compare
- How much faster is the C version?
- Generate a flamegraph for `prime_fast.py` - what's different?
## Discussion Questions
1. Why is the C version faster? (Hint: interpreter overhead, type checking)
2. When is it worth rewriting in C vs. finding a library?
3. What's the trade-off of using `ctypes` vs native Python?

View File

@@ -0,0 +1,36 @@
/*
* Scenario 1: C implementation of is_prime
* =========================================
* Compile as shared library:
* gcc -O2 -fPIC -shared -o libprime.so prime.c
*
* Or with debug symbols for profiling:
* gcc -O2 -g -fPIC -shared -o libprime.so prime.c
*/
#include <stdint.h>
/* Check if n is prime using trial division */
int is_prime(int64_t n) {
if (n < 2) return 0;
if (n == 2) return 1;
if (n % 2 == 0) return 0;
int64_t i = 3;
while (i * i <= n) {
if (n % i == 0) return 0;
i += 2;
}
return 1;
}
/* Count primes up to limit - can also be called from Python */
int64_t count_primes(int64_t limit) {
int64_t count = 0;
for (int64_t n = 2; n <= limit; n++) {
if (is_prime(n)) {
count++;
}
}
return count;
}

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
Scenario 1: The Optimized Version
=================================
This version calls the C implementation via ctypes.
SETUP:
gcc -O2 -fPIC -shared -o libprime.so prime.c
EXERCISES:
1. Compare: time python3 prime_fast.py
2. Profile: py-spy record -o prime_fast.svg -- python3 prime_fast.py
3. Compare the flamegraphs - what changed?
"""
import ctypes
import sys
import os
# Load the shared library
script_dir = os.path.dirname(os.path.abspath(__file__))
lib_path = os.path.join(script_dir, "libprime.so")
try:
libprime = ctypes.CDLL(lib_path)
except OSError as e:
print(f"Error: Could not load {lib_path}")
print("Please compile first: gcc -O2 -fPIC -shared -o libprime.so prime.c")
sys.exit(1)
# Define function signatures
libprime.is_prime.argtypes = [ctypes.c_int64]
libprime.is_prime.restype = ctypes.c_int
libprime.count_primes.argtypes = [ctypes.c_int64]
libprime.count_primes.restype = ctypes.c_int64
def is_prime(n):
"""Python wrapper for C is_prime."""
return bool(libprime.is_prime(n))
def count_primes(limit):
"""Python wrapper for C count_primes."""
return libprime.count_primes(limit)
def main():
limit = 1_000_000
if len(sys.argv) > 1:
limit = int(sys.argv[1])
print(f"Counting primes up to {limit} (using C library)...")
result = count_primes(limit)
print(f"Found {result} primes")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""
Scenario 1: The Obvious CPU Hog
===============================
This program counts prime numbers using trial division.
It's intentionally slow to demonstrate profiling.
EXERCISES:
1. Run with: time python3 prime_slow.py
2. Profile with: python3 -m cProfile -s cumtime prime_slow.py
3. Generate flamegraph: py-spy record -o prime_slow.svg -- python3 prime_slow.py
4. Identify the hot function, then see prime_fast.py for the optimized version
"""
import sys
def is_prime(n):
"""Check if n is prime using trial division."""
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
# Check odd divisors up to sqrt(n)
i = 3
while i * i <= n:
if n % i == 0:
return False
i += 2
return True
def count_primes(limit):
"""Count all primes up to limit."""
count = 0
for n in range(2, limit + 1):
if is_prime(n):
count += 1
return count
def main():
limit = 1_000_000 # Adjust this to change runtime
if len(sys.argv) > 1:
limit = int(sys.argv[1])
print(f"Counting primes up to {limit}...")
result = count_primes(limit)
print(f"Found {result} primes")
if __name__ == "__main__":
main()