10_performance.py

Download
python 470 lines 11.7 KB
  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""")