Skip to content
← Back to explainers

Python explainer

Measure What slots=True Saves

About 4 min to read. Allow extra time to work through examples.

Measure how much memory slots=True saves on your own records with tracemalloc, keep the comparison fair, and time speed in a separate run.

This article uses Python. You can make it your practice mode to browse related content across the site.

Your service keeps a few million small records in memory, and you want to know whether slots=True is worth the change. T3's article Save Memory with slots=True explains what slots do and what a slotted class gives up. This article is about the measurement. A percentage from someone else's benchmark does not tell you what your records on your Python will save.

Name what you are measuring

Each question has its own tool, and one answer can change while another stays the same:

Question Tool
How big is this one object, without what it points at? sys.getsizeof, which gives the shallow size
How much memory do these records hold while they are alive? The change in tracemalloc's current traced memory
How high does memory get while you build them? tracemalloc's peak
How much RAM does the process use? The operating system's RSS
How long does it take to build or read a record? A separate timing run, with tracing off

Compare two batches of records

This script builds 100,000 records of each class and measures the traced memory they hold:

import gc
import platform
import tracemalloc
from dataclasses import dataclass

@dataclass
class PlainPoint:
    x: int
    y: int

@dataclass(slots=True)
class SlottedPoint:
    x: int
    y: int

def measure(factory: type[PlainPoint] | type[SlottedPoint],
            count: int) -> tuple[int, int]:
    if count <= 0:
        raise ValueError("count must be positive")
    records: list[object] = [None] * count
    gc.collect()
    tracemalloc.start()
    before, _ = tracemalloc.get_traced_memory()
    tracemalloc.reset_peak()
    for index in range(count):
        records[index] = factory(1, 2)
    current, peak = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    return current - before, peak - before

print(platform.python_implementation(), platform.python_version(), platform.platform())
count = 100_000
for factory in (PlainPoint, SlottedPoint):
    retained, peak = measure(factory, count)
    print(factory.__name__)
    print("retained bytes:", retained, "per record:", retained / count)
    print("peak bytes above baseline:", peak)

Two choices keep the comparison fair. The script builds the records list before tracing starts, so the list itself is not part of the result. And every record holds the same two small integers, which Python shares, so the loop allocates no new integers.

On a Mac with Python 3.12, a PlainPoint takes about 80 bytes and a SlottedPoint about 48. On Python 3.15 the plain record takes about 88 bytes, and the slotted one stays at 48. The T3 article reports about 8 bytes more per record, because it builds the list while tracing is on.

Run the script in a fresh process, with no other tracing session. Then vary the experiment:

  • Swap the order of the two classes, and try several batch sizes. Report the batch totals, not only the bytes per record.
  • Run it again with your real data, such as distinct strings or nested lists. When each record holds a long string, the string takes most of the memory, and the saving from slots matters less.
Check your understanding Traced memory falls by 30 MB after the switch, but the process's RSS barely changes. Is the measurement wrong? Show answer

Not necessarily. The records may be a small part of the process's memory. Python also keeps freed memory for reuse instead of giving it back to the operating system at once, so RSS can stay high. Check the application's memory under real load as a separate measurement.

Measure speed separately

A smaller record is not automatically a faster one. Building a record and reading its attributes are separate operations, and you have to time each one. Turn tracing off first, because it slows every allocation. The timing articles later in this track show how to measure small pieces of code.

Decide from the application

Before you switch, read the T3 article's list of what a slotted class gives up, such as extra attributes, cached_property and weak references. Then measure the application under a realistic load. A useful result says how much memory or time changed, at how many records, and whether that change matters for the service.

Going further

The Python documentation covers tracemalloc and sys.getsizeof.

step 20 of 38

Memory, Copies and Slow Code

Up next Use a Set for Lookups and a Deque for Queues