Python Memory Optimization & Cyclic Garbage Collection
August 5, 20266 min read
Python
Performance
Memory
Internals
Memory optimization in high-throughput Python applications requires a clear understanding of CPython’s internal memory manager (PyMalloc) and cyclic garbage collector (gc module).
Reference Counting & PyObject
Every Python object has a ob_refcnt header. When references are created or destroyed, ob_refcnt increments or decrements immediately.
import sys
class Node:
def __init__(self, value: int):
self.value = value
self.neighbor = None
a = Node(100)
print("Initial ref count:", sys.getrefcount(a) - 1) # -1 for sys.getrefcount temporary ref
b = a
print("After assignment ref count:", sys.getrefcount(a) - 1)
Reference Cycles & Cyclic GC
Reference counting fails when objects reference each other in a closed cycle:
import gc
def create_cycle():
node1 = Node(1)
node2 = Node(2)
# Creating a cyclic reference
node1.neighbor = node2
node2.neighbor = node1
# Disable automatic GC for demonstration
gc.disable()
create_cycle()
print("Unreachable objects detected:", gc.collect())
Optimizing Memory with __slots__
By default, Python instances use a dynamic __dict__ dictionary to store attributes. For millions of objects, this creates huge memory overhead.
import sys
class StandardPoint:
def __init__(self, x: float, y: float, z: float):
self.x = x
self.y = y
self.z = z
class SlottedPoint:
__slots__ = ('x', 'y', 'z')
def __init__(self, x: float, y: float, z: float):
self.x = x
self.y = y
self.z = z
std_p = StandardPoint(1.0, 2.0, 3.0)
slot_p = SlottedPoint(1.0, 2.0, 3.0)
print("Standard instance size + dict:", sys.getsizeof(std_p) + sys.getsizeof(std_p.__dict__))
print("Slotted instance size:", sys.getsizeof(slot_p))
Production Guidelines
- Use
__slots__for lightweight data structures created in high volume. - Break circular references explicitly or use
weakref.reffor back-pointers. - Monitor heap allocations using
tracemallocin staging and load testing.