Python 3.11 - Complexity & Optimizations¶
Python 3.11 was released October 2022 with major performance improvements including inline caching.
Major Performance Improvements¶
Inline Caching¶
CPython 3.11 introduced inline caching for faster attribute access and method calls:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# First access: Cache miss, dict lookup
print(p.x) # O(1) + dict search
# Second+ access: Uses inline cache
for _ in range(1000000):
print(p.x) # Very fast (inline cache hit)
# Result: ~2x faster attribute access
Optimized Bytecode¶
Specialized bytecode for common operations:
# Binary operations optimize for common types
def add_ints(a, b):
return a + b
# Specialized for int + int after first calls
for i in range(1000000):
result = add_ints(i, i+1)
# ~2-3x faster than 3.10
Performance Metrics¶
Overall Improvements¶
Geometric mean: 10% faster across all benchmarks
Best case: 60% faster (for specific patterns)
Typical: 10-30% faster
Worst case: Similar to 3.10
Specific Operations¶
# Attribute access: 2-4x faster
p.x # Cache helps
# Method calls: 1.5-3x faster
obj.method() # Inline caching
# Integer arithmetic: 2-3x faster
a + b # Specialized bytecode
# String operations: Similar (still O(n) but with lower constants)
s.find(x) # Optimized but same complexity
# List operations: Similar algorithmic complexity
lst.append(x) # O(1) amortized, faster in practice
Complexity Characteristics¶
Standard Complexities Preserved¶
All standard complexities remain the same as Python 3.10:
| Type | Operation | Complexity | Status |
|---|---|---|---|
| list | append | O(1) amortized | Unchanged |
| list | insert | O(n) | Unchanged |
| dict | lookup | O(1) avg | Unchanged |
| set | add | O(1) amortized | Unchanged |
| str | find | O(n*m) | Unchanged |
*Amortized or average case, but faster in practice
New Features¶
Exception Groups¶
# Exception groups and except* syntax
try:
# Multiple potential errors
results = []
for item in items:
results.append(risky_operation(item))
except* ValueError as eg:
for exc in eg.exceptions:
handle_value_error(exc)
except* RuntimeError as eg:
for exc in eg.exceptions:
handle_runtime_error(exc)
Fine-grained Error Locations¶
# Better error messages with exact location
x = [1, 2, 3]
x[999]
# Old: IndexError: list index out of range
# New: IndexError: list index out of range (includes location info)
# Traceback shows exact problem location
Tomllib Support¶
# Read TOML configuration files (Python 3.11+)
import tomllib
with open('config.toml', 'rb') as f:
config = tomllib.load(f)
Memory Usage¶
Inline Cache Impact¶
Slight memory increase per function:
# Inline caches stored in bytecode
# ~128 bytes per function for cache entries
# Not significant for most applications
Example: Function Memory¶
import sys
def simple():
return 1
print(sys.getsizeof(simple.__code__)) # Bytecode with caches
# Slightly larger than 3.10, but cached speeds compensate
Complexity by Data Structure¶
Lists¶
# Same complexity as 3.10
lst = []
lst.append(1) # O(1) amortized
lst.extend([2,3]) # O(k) for k items
lst.insert(0, 0) # O(n) shift all
lst.pop() # O(1)
lst.pop(0) # O(n) shift all
lst.sort() # O(n log n) Timsort
lst[i] # O(1)
1 in lst # O(n) search
Dictionaries¶
# Same complexity as 3.10, insertion order guaranteed
d = {}
d['key'] = 'val' # O(1) avg
d['key'] # O(1) avg
del d['key'] # O(1) avg
'key' in d # O(1) avg
d.keys() # O(1) view
d.values() # O(1) view
d.items() # O(1) view
d.get('key') # O(1) avg
# Iteration still O(n)
for k, v in d.items(): # O(n)
pass
Sets¶
# Same complexity as 3.10
s = set()
s.add(1) # O(1) avg
1 in s # O(1) avg
s.remove(1) # O(1) avg
s.discard(1) # O(1) avg
s | {2} # O(n+m) union
s & {2} # O(min(n,m)) intersection
s - {2} # O(n) difference
Compatibility Notes¶
Breaking Changes¶
Minimal breaking changes from 3.10:
# Removed:
# - asyncore, asynchat modules
# - smtpd module
# - aifc, audioop, chunk, cgi, cgitb, imaplib, mailcap, nis, nntplib, spwd, sunau, xdrlib modules
# Deprecated (warnings only):
# - Some unittest methods
# - Various other APIs
# Check: python -W all your_script.py
Performance-Critical Code¶
Performance improvements apply without code changes:
# Just upgrade Python version
# Code runs 10-30% faster automatically
# No changes needed!
def compute_heavy():
result = 0
for i in range(1000000):
result += i * i
return result
# 3.10: baseline
# 3.11: ~30% faster (due to specialization)
Recommendations¶
When to Upgrade to 3.11¶
Strongly recommended if: - ✅ Performance matters - ✅ Deployed as long-running service - ✅ Handling high request volume - ✅ CPU-bound operations
Consider if: - ✅ Need better error messages - ✅ Using exception groups - ✅ Want modern Python features
Deployment Impact¶
# Upgrade is generally safe
# Just change Python version, re-run tests
# Expected: Code runs significantly faster
# Risk: Low (minimal breaking changes)
# Effort: Minimal (usually just version change)
Performance Testing¶
Benchmark Your Code¶
import time
def benchmark(func, *args, runs=1000):
start = time.perf_counter()
for _ in range(runs):
func(*args)
elapsed = time.perf_counter() - start
return elapsed / runs
# Test on both Python 3.10 and 3.11
result_310 = benchmark(your_function) # On Python 3.10
result_311 = benchmark(your_function) # On Python 3.11
print(f"Speedup: {result_310 / result_311:.1f}x")