1"""
2Python Performance Optimization
3
4Demonstrates:
5- timeit module
6- cProfile profiling
7- __slots__ for memory optimization
8- List comprehensions vs loops
9- String concatenation performance
10- collections module optimizations
11- Generator expressions
12- Local variable lookups
13"""
14
15import timeit
16import cProfile
17import pstats
18import io
19import sys
20from collections import defaultdict, Counter, deque, namedtuple
21
22
23def section(title: str) -> None:
24 """Print a section header."""
25 print("\n" + "=" * 60)
26 print(f" {title}")
27 print("=" * 60)
28
29
30# =============================================================================
31# timeit - Measuring Execution Time
32# =============================================================================
33
34section("timeit - Measuring Execution Time")
35
36# Simple timing
37def slow_loop():
38 result = []
39 for i in range(1000):
40 result.append(i ** 2)
41 return result
42
43
44def fast_comprehension():
45 return [i ** 2 for i in range(1000)]
46
47
48# Time with timeit.timeit
49time1 = timeit.timeit(slow_loop, number=1000)
50time2 = timeit.timeit(fast_comprehension, number=1000)
51
52print(f"Loop: {time1:.4f} seconds")
53print(f"Comprehension: {time2:.4f} seconds")
54print(f"Speedup: {time1 / time2:.2f}x")
55
56
57# Time with timeit.repeat
58times = timeit.repeat('"-".join(str(i) for i in range(100))', number=1000, repeat=5)
59print(f"\nString join (5 runs):")
60print(f" Min: {min(times):.4f}s")
61print(f" Max: {max(times):.4f}s")
62print(f" Avg: {sum(times)/len(times):.4f}s")
63
64
65# =============================================================================
66# List Comprehensions vs Loops
67# =============================================================================
68
69section("List Comprehensions vs Loops")
70
71
72def with_loop(n):
73 result = []
74 for i in range(n):
75 if i % 2 == 0:
76 result.append(i ** 2)
77 return result
78
79
80def with_comprehension(n):
81 return [i ** 2 for i in range(n) if i % 2 == 0]
82
83
84n = 10000
85t1 = timeit.timeit(lambda: with_loop(n), number=100)
86t2 = timeit.timeit(lambda: with_comprehension(n), number=100)
87
88print(f"Loop: {t1:.4f}s")
89print(f"Comprehension: {t2:.4f}s")
90print(f"Speedup: {t1/t2:.2f}x")
91
92
93# =============================================================================
94# String Concatenation
95# =============================================================================
96
97section("String Concatenation Performance")
98
99
100def concat_with_plus(n):
101 """Slow - creates new string each iteration."""
102 result = ""
103 for i in range(n):
104 result += str(i)
105 return result
106
107
108def concat_with_join(n):
109 """Fast - builds list then joins once."""
110 parts = []
111 for i in range(n):
112 parts.append(str(i))
113 return "".join(parts)
114
115
116def concat_with_comprehension(n):
117 """Fastest - comprehension + join."""
118 return "".join(str(i) for i in range(n))
119
120
121n = 1000
122t1 = timeit.timeit(lambda: concat_with_plus(n), number=100)
123t2 = timeit.timeit(lambda: concat_with_join(n), number=100)
124t3 = timeit.timeit(lambda: concat_with_comprehension(n), number=100)
125
126print(f"Plus operator: {t1:.4f}s")
127print(f"Join with list: {t2:.4f}s")
128print(f"Join with generator: {t3:.4f}s")
129print(f"Plus vs Join speedup: {t1/t2:.2f}x")
130
131
132# =============================================================================
133# __slots__ for Memory Optimization
134# =============================================================================
135
136section("__slots__ for Memory Optimization")
137
138
139class PointWithDict:
140 """Regular class with __dict__."""
141 def __init__(self, x, y):
142 self.x = x
143 self.y = y
144
145
146class PointWithSlots:
147 """Class with __slots__ - no __dict__."""
148 __slots__ = ['x', 'y']
149
150 def __init__(self, x, y):
151 self.x = x
152 self.y = y
153
154
155# Compare memory usage
156p1 = PointWithDict(10, 20)
157p2 = PointWithSlots(10, 20)
158
159print(f"PointWithDict size: {sys.getsizeof(p1) + sys.getsizeof(p1.__dict__)} bytes")
160print(f"PointWithSlots size: {sys.getsizeof(p2)} bytes")
161
162# Time attribute access
163t1 = timeit.timeit('p.x', globals={'p': p1}, number=1000000)
164t2 = timeit.timeit('p.x', globals={'p': p2}, number=1000000)
165
166print(f"\nAttribute access:")
167print(f" With __dict__: {t1:.4f}s")
168print(f" With __slots__: {t2:.4f}s")
169
170
171# =============================================================================
172# collections.defaultdict
173# =============================================================================
174
175section("collections.defaultdict")
176
177
178def count_words_dict(words):
179 """Count words with regular dict."""
180 counts = {}
181 for word in words:
182 if word in counts:
183 counts[word] += 1
184 else:
185 counts[word] = 1
186 return counts
187
188
189def count_words_defaultdict(words):
190 """Count words with defaultdict."""
191 counts = defaultdict(int)
192 for word in words:
193 counts[word] += 1
194 return counts
195
196
197words = ["apple", "banana", "apple", "cherry", "banana", "apple"] * 1000
198
199t1 = timeit.timeit(lambda: count_words_dict(words), number=100)
200t2 = timeit.timeit(lambda: count_words_defaultdict(words), number=100)
201
202print(f"Regular dict: {t1:.4f}s")
203print(f"defaultdict: {t2:.4f}s")
204print(f"Speedup: {t1/t2:.2f}x")
205
206
207# =============================================================================
208# collections.Counter
209# =============================================================================
210
211section("collections.Counter")
212
213
214def count_manual(items):
215 counts = {}
216 for item in items:
217 counts[item] = counts.get(item, 0) + 1
218 return counts
219
220
221def count_with_counter(items):
222 return Counter(items)
223
224
225items = list(range(100)) * 100
226
227t1 = timeit.timeit(lambda: count_manual(items), number=100)
228t2 = timeit.timeit(lambda: count_with_counter(items), number=100)
229
230print(f"Manual counting: {t1:.4f}s")
231print(f"Counter: {t2:.4f}s")
232print(f"Speedup: {t1/t2:.2f}x")
233
234# Counter features
235counter = Counter(['a', 'b', 'c', 'a', 'b', 'a'])
236print(f"\nCounter example: {counter}")
237print(f"Most common: {counter.most_common(2)}")
238
239
240# =============================================================================
241# collections.deque
242# =============================================================================
243
244section("collections.deque vs list")
245
246
247def append_left_list(n):
248 """Slow - O(n) for each insert."""
249 lst = []
250 for i in range(n):
251 lst.insert(0, i)
252 return lst
253
254
255def append_left_deque(n):
256 """Fast - O(1) for each appendleft."""
257 dq = deque()
258 for i in range(n):
259 dq.appendleft(i)
260 return dq
261
262
263n = 1000
264t1 = timeit.timeit(lambda: append_left_list(n), number=10)
265t2 = timeit.timeit(lambda: append_left_deque(n), number=10)
266
267print(f"list.insert(0, x): {t1:.4f}s")
268print(f"deque.appendleft(x): {t2:.4f}s")
269print(f"Speedup: {t1/t2:.2f}x")
270
271
272# =============================================================================
273# Generator Expressions vs List Comprehensions
274# =============================================================================
275
276section("Generator vs List Comprehension")
277
278
279def sum_list_comp(n):
280 """Creates entire list in memory."""
281 return sum([i ** 2 for i in range(n)])
282
283
284def sum_generator(n):
285 """Lazy evaluation - no intermediate list."""
286 return sum(i ** 2 for i in range(n))
287
288
289n = 100000
290t1 = timeit.timeit(lambda: sum_list_comp(n), number=100)
291t2 = timeit.timeit(lambda: sum_generator(n), number=100)
292
293print(f"List comprehension: {t1:.4f}s")
294print(f"Generator expression: {t2:.4f}s")
295
296# Memory comparison
297import sys
298list_size = sys.getsizeof([i for i in range(10000)])
299gen_size = sys.getsizeof(i for i in range(10000))
300print(f"\nMemory (10k items):")
301print(f" List: {list_size} bytes")
302print(f" Generator: {gen_size} bytes")
303
304
305# =============================================================================
306# Local Variable Lookups
307# =============================================================================
308
309section("Local Variable Lookups")
310
311
312def with_global_lookup():
313 """Slower - global lookup each iteration."""
314 result = 0
315 for i in range(10000):
316 result += len(str(i))
317 return result
318
319
320def with_local_lookup():
321 """Faster - local variable."""
322 _len = len
323 _str = str
324 result = 0
325 for i in range(10000):
326 result += _len(_str(i))
327 return result
328
329
330t1 = timeit.timeit(with_global_lookup, number=100)
331t2 = timeit.timeit(with_local_lookup, number=100)
332
333print(f"Global lookups: {t1:.4f}s")
334print(f"Local lookups: {t2:.4f}s")
335print(f"Speedup: {t1/t2:.2f}x")
336
337
338# =============================================================================
339# namedtuple vs dict
340# =============================================================================
341
342section("namedtuple vs dict")
343
344Point = namedtuple('Point', ['x', 'y'])
345
346
347def create_dicts(n):
348 return [{'x': i, 'y': i*2} for i in range(n)]
349
350
351def create_tuples(n):
352 return [Point(i, i*2) for i in range(n)]
353
354
355n = 10000
356t1 = timeit.timeit(lambda: create_dicts(n), number=100)
357t2 = timeit.timeit(lambda: create_tuples(n), number=100)
358
359print(f"dict creation: {t1:.4f}s")
360print(f"namedtuple creation: {t2:.4f}s")
361print(f"Speedup: {t1/t2:.2f}x")
362
363# Access time
364d = {'x': 10, 'y': 20}
365t = Point(10, 20)
366
367t1 = timeit.timeit('d["x"]', globals={'d': d}, number=1000000)
368t2 = timeit.timeit('t.x', globals={'t': t}, number=1000000)
369
370print(f"\nAccess time:")
371print(f" dict['x']: {t1:.4f}s")
372print(f" tuple.x: {t2:.4f}s")
373
374
375# =============================================================================
376# cProfile - Profiling Code
377# =============================================================================
378
379section("cProfile - Profiling Code")
380
381
382def fibonacci(n):
383 """Inefficient recursive Fibonacci."""
384 if n <= 1:
385 return n
386 return fibonacci(n - 1) + fibonacci(n - 2)
387
388
389def test_function():
390 """Function to profile."""
391 results = []
392 for i in range(15):
393 results.append(fibonacci(i))
394 return results
395
396
397# Profile the function
398profiler = cProfile.Profile()
399profiler.enable()
400test_function()
401profiler.disable()
402
403# Print stats
404s = io.StringIO()
405ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
406ps.print_stats(10)
407
408print("Top 10 functions by cumulative time:")
409print(s.getvalue()[:800]) # Print first 800 chars
410
411
412# =============================================================================
413# Set Membership
414# =============================================================================
415
416section("Set vs List Membership")
417
418
419def search_in_list(items, targets):
420 """O(n) lookup."""
421 return [item in items for item in targets]
422
423
424def search_in_set(items, targets):
425 """O(1) lookup."""
426 item_set = set(items)
427 return [item in item_set for item in targets]
428
429
430items = list(range(1000))
431targets = list(range(500, 1500))
432
433t1 = timeit.timeit(lambda: search_in_list(items, targets), number=100)
434t2 = timeit.timeit(lambda: search_in_set(items, targets), number=100)
435
436print(f"List membership: {t1:.4f}s")
437print(f"Set membership: {t2:.4f}s")
438print(f"Speedup: {t1/t2:.2f}x")
439
440
441# =============================================================================
442# Summary
443# =============================================================================
444
445section("Summary")
446
447print("""
448Performance optimization techniques:
4491. timeit - measure execution time accurately
4502. List comprehensions - faster than loops
4513. String join - faster than concatenation
4524. __slots__ - reduce memory for many instances
4535. collections.defaultdict - avoid key checks
4546. collections.Counter - optimized counting
4557. collections.deque - fast append/pop from both ends
4568. Generator expressions - memory efficient
4579. Local variable lookups - faster than global
45810. namedtuple - faster and lighter than dict
45911. cProfile - identify performance bottlenecks
46012. Set membership - O(1) vs O(n) for lists
461
462General principles:
463- Measure before optimizing (profile first!)
464- Use built-in functions and types
465- Avoid premature optimization
466- Choose right data structure for use case
467- Use generators for large datasets
468- Cache expensive computations
469""")