-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbenchmark.py
More file actions
executable file
·175 lines (138 loc) · 5.66 KB
/
benchmark.py
File metadata and controls
executable file
·175 lines (138 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env python3
"""
Main benchmark script to compare Python vs Cython performance.
This script benchmarks the generation of a 1 million row pandas DataFrame
and writing it to a parquet file with snappy compression.
"""
import os
import sys
import time
from typing import Dict, List, Tuple
# Import implementations
import python_impl
import cython_impl
def run_multiple_benchmarks(num_iterations: int = 5, num_rows: int = 1_000_000) -> Dict[str, List[Tuple[float, float]]]:
"""
Run benchmarks multiple times to get average results.
Args:
num_iterations: Number of times to run each benchmark
num_rows: Number of rows in the DataFrame
Returns:
Dictionary with results for each implementation
"""
results = {
'python': [],
'cython': []
}
print(f"Running benchmarks with {num_rows:,} rows...")
print(f"Each benchmark will be run {num_iterations} times for accuracy.\n")
for i in range(num_iterations):
print(f"Iteration {i + 1}/{num_iterations}")
# Python benchmark
print(" Running Pure Python implementation...")
py_gen_time, py_write_time = python_impl.run_benchmark(
num_rows, f'output_python_{i}.parquet'
)
results['python'].append((py_gen_time, py_write_time))
print(f" Generation: {py_gen_time:.4f}s, Writing: {py_write_time:.4f}s, Total: {py_gen_time + py_write_time:.4f}s")
# Cython benchmark
print(" Running Cython implementation...")
cy_gen_time, cy_write_time = cython_impl.run_benchmark_cython(
num_rows, f'output_cython_{i}.parquet'
)
results['cython'].append((cy_gen_time, cy_write_time))
print(f" Generation: {cy_gen_time:.4f}s, Writing: {cy_write_time:.4f}s, Total: {cy_gen_time + cy_write_time:.4f}s")
print()
return results
def calculate_statistics(results: Dict[str, List[Tuple[float, float]]]) -> Dict[str, Dict[str, float]]:
"""
Calculate average statistics from benchmark results.
Args:
results: Raw benchmark results
Returns:
Dictionary with calculated statistics
"""
stats = {}
for impl_name, times_list in results.items():
gen_times = [t[0] for t in times_list]
write_times = [t[1] for t in times_list]
total_times = [t[0] + t[1] for t in times_list]
stats[impl_name] = {
'avg_gen_time': sum(gen_times) / len(gen_times),
'avg_write_time': sum(write_times) / len(write_times),
'avg_total_time': sum(total_times) / len(total_times),
'min_total_time': min(total_times),
'max_total_time': max(total_times),
}
return stats
def print_results(stats: Dict[str, Dict[str, float]]) -> None:
"""
Print formatted benchmark results.
Args:
stats: Calculated statistics
"""
print("=" * 80)
print("BENCHMARK RESULTS")
print("=" * 80)
print()
print("Pure Python Implementation:")
print(f" Average Generation Time: {stats['python']['avg_gen_time']:.4f}s")
print(f" Average Writing Time: {stats['python']['avg_write_time']:.4f}s")
print(f" Average Total Time: {stats['python']['avg_total_time']:.4f}s")
print(f" Min Total Time: {stats['python']['min_total_time']:.4f}s")
print(f" Max Total Time: {stats['python']['max_total_time']:.4f}s")
print()
print("Cython Implementation:")
print(f" Average Generation Time: {stats['cython']['avg_gen_time']:.4f}s")
print(f" Average Writing Time: {stats['cython']['avg_write_time']:.4f}s")
print(f" Average Total Time: {stats['cython']['avg_total_time']:.4f}s")
print(f" Min Total Time: {stats['cython']['min_total_time']:.4f}s")
print(f" Max Total Time: {stats['cython']['max_total_time']:.4f}s")
print()
print("Performance Comparison:")
gen_speedup = stats['python']['avg_gen_time'] / stats['cython']['avg_gen_time']
write_speedup = stats['python']['avg_write_time'] / stats['cython']['avg_write_time']
total_speedup = stats['python']['avg_total_time'] / stats['cython']['avg_total_time']
print(f" Generation Speedup: {gen_speedup:.2f}x")
print(f" Writing Speedup: {write_speedup:.2f}x")
print(f" Total Speedup: {total_speedup:.2f}x")
print()
if total_speedup > 1:
improvement = ((total_speedup - 1) * 100)
print(f" Cython is {improvement:.1f}% faster than Pure Python")
else:
degradation = ((1 - total_speedup) * 100)
print(f" Cython is {degradation:.1f}% slower than Pure Python")
print()
print("=" * 80)
def cleanup_output_files() -> None:
"""Remove generated parquet files."""
import glob
for pattern in ['output_python_*.parquet', 'output_cython_*.parquet']:
for filepath in glob.glob(pattern):
try:
os.remove(filepath)
except OSError:
pass
def main():
"""Main entry point for the benchmark."""
print("=" * 80)
print("Python vs Cython Benchmark")
print("DataFrame Generation and Parquet Writing Performance")
print("=" * 80)
print()
# Configuration
num_iterations = 5
num_rows = 1_000_000
# Run benchmarks
results = run_multiple_benchmarks(num_iterations, num_rows)
# Calculate statistics
stats = calculate_statistics(results)
# Print results
print_results(stats)
# Cleanup
print("Cleaning up output files...")
cleanup_output_files()
print("Done!")
if __name__ == '__main__':
main()