Fixed-Offset Memory Layout Optimization (__slots__)

Master CPython instance memory optimization: suppressing __dict__ via __slots__, PyMemberDescrObject struct offsets, memory benchmarking, and inheritance layout constraints.

03. Object-Oriented Systems, Descriptor Protocol & Metaprogramming7 min read

By default, every Python instance allocates an internal dictionary __dict__ to store dynamic attributes. While this enables adding new attributes on the fly, it imposes massive memory overhead for high-volume objects: a single standard object can easily consume over 150 bytes of RAM.

When building applications that instantiate millions of records (such as financial ticks, game entities, or scientific nodes), specifying __slots__ optimizes memory layouts by replacing the per-instance dictionary with a compact C struct array of fixed-offset pointer slots.

Memory Footprint ReductionOptimization
85%

Eliminating instance __dict__ reduces memory per instance from ~152 bytes to ~48 bytes.

1. Comparing Standard vs Slotted Memory Layouts#

Architecture Diagram
Rendering diagram...

Mathematical Footprint Derivation#

Let S=(s1,s2,…,sk)S = (s_1, s_2, \dots, s_k) represent attribute names defined in __slots__:

Mdict(obj)=sizeof(PyObject)+sizeof(PyDictObject)+Entries Array OverheadM_{\text{dict}}(obj) = \text{sizeof}(\text{PyObject}) + \text{sizeof}(\text{PyDictObject}) + \text{Entries Array Overhead}
Mslots(obj)=sizeof(PyObject)+k⋅sizeof(PyObject*)M_{\text{slots}}(obj) = \text{sizeof}(\text{PyObject}) + k \cdot \text{sizeof}(\text{PyObject*})

The memory compression ratio ρ\rho demonstrates substantial reduction:

ρ=Mslots(obj)Mdict(obj)≈0.15 to 0.30(70% to 85% reduction)\rho = \frac{M_{\text{slots}}(obj)}{M_{\text{dict}}(obj)} \approx 0.15 \text{ to } 0.30 \quad (70\% \text{ to } 85\% \text{ reduction})

2. C-API Implementation: Member Descriptors#

How does Python resolve attribute reads on slotted classes without a dictionary?

When __slots__ is declared:

  1. type.__new__ calculates instance memory size tp_basicsize as sizeof(PyObject)+k⋅8\text{sizeof}(\text{PyObject}) + k \cdot 8 bytes.
  2. For each attribute name in __slots__, CPython creates a descriptor of type PyMemberDescrObject and places it in the class dictionary.
  3. Each member descriptor encapsulates a fixed byte offset Δi\Delta_i from the instance base address.
  4. Accessing obj.x executes the descriptor's __get__, which reads object memory directly at address ptr(obj)+Δi\text{ptr}(obj) + \Delta_i, executing as a single-instruction pointer dereference bypassing dictionary hash lookups entirely!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys class StandardPoint: def __init__(self, x: float, y: float): self.x = x self.y = y class SlottedPoint: __slots__ = ("x", "y") def __init__(self, x: float, y: float): self.x = x self.y = y std_p = StandardPoint(1.0, 2.0) slt_p = SlottedPoint(1.0, 2.0) print(f"Standard instance size : {sys.getsizeof(std_p)} bytes (excluding __dict__)") print(f"Standard __dict__ size : {sys.getsizeof(std_p.__dict__)} bytes") print(f"Total Standard Footprint: {sys.getsizeof(std_p) + sys.getsizeof(std_p.__dict__)} bytes") print(f"Slotted Total Footprint : {sys.getsizeof(slt_p)} bytes")

3. Critical Inheritance Traps with __slots__#

While __slots__ delivers exceptional space savings, it introduces rigid structural constraints:

Trap 1: Subclasses Re-introduce __dict__
If a subclass inherits from a slotted class but fails to declare __slots__ itself, CPython will automatically allocate an instance dictionary __dict__ for instances of the subclass, wiping out your memory savings! To maintain the optimization, subclasses must explicitly define: ``python class SubSlotted(SlottedPoint): __slots__ = () # Keeps __dict__ suppressed!
Trap 2: Multiple Inheritance Layout Conflict
A class cannot inherit from multiple parent classes that both define non-empty __slots__. Because each parent attempts to place its slot attributes at conflicting byte offsets within the instance struct, CPython aborts with ValueError: multiple bases have instance lay-out conflict.

4. Concept Verification#

Test your understanding of __slots__ memory optimization:

Concept check

How does CPython read and write attributes on a class that defines __slots__ = ('x', 'y') without an instance dictionary?

Concept check

What happens when a child class inherits from a slotted parent class but does not declare __slots__ itself?