Skip to content

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")