init
This commit is contained in:
55
scenario1-python-to-c/README.md
Normal file
55
scenario1-python-to-c/README.md
Normal 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?
|
||||
36
scenario1-python-to-c/prime.c
Normal file
36
scenario1-python-to-c/prime.c
Normal 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;
|
||||
}
|
||||
60
scenario1-python-to-c/prime_fast.py
Normal file
60
scenario1-python-to-c/prime_fast.py
Normal 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()
|
||||
55
scenario1-python-to-c/prime_slow.py
Normal file
55
scenario1-python-to-c/prime_slow.py
Normal 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()
|
||||
Reference in New Issue
Block a user