Skip to content

← Under the Hood: Objects, Memory, Speed step 19 of 35

Medium Framework

find_growth: measure retention, and stop the tracer on every path

Implement the measurement that distinguishes “this function allocates” from “this function leaks”.

def find_growth(fn: Callable[[], None], *, iterations: int) -> int: ...

Return the net traced bytes retained by running fn iterations times: start the tracer, warm up with one call, snapshot, run iterations times, snapshot, and sum size_diff over after.compare_to(before, "lineno").

The tracer must be stopped on every path, including when fn raises — and the exception must still propagate. tracemalloc is global process state; a tracer left running silently changes the overhead of every subsequent measurement in the process, which is a genuinely nasty way to spend an afternoon.

Three workloads are provided:

name behaviour
"leaks" appends a 1,000-element list to a module-level SINK
"churns" allocates a 1,000-element list and immediately releases it
"explodes" raises ValueError

solve(workload, iterations) calls find_growth at iterations and then again at 2 * iterations, clearing SINK between the two so the runs are independent, and returns:

{"grew": bool, "scaled": bool, "raised": str, "tracing_stopped": bool}

where grew is “first measurement exceeded 100 KB”, scaled is “it grew and doubling the iteration count grew it by at least 1.5x”, raised is "" or "ValueError", and tracing_stopped reports not tracemalloc.is_tracing() after everything.

Why the warm-up matters. First-call allocations — an import, a compiled regex, a lazily built cache, a connection — are one-time costs. Snapshot before them and they dominate the diff, so a function that allocates 2 MB once and nothing thereafter looks exactly like a leak. One warm-up call removes the entire category.

Why the doubling matters. Growth that scales with the iteration count is a leak. Growth that does not is what a call costs. Measuring at one N cannot tell them apart, and that distinction is the whole reason anyone runs this.

Your submission must pass mypy --strict.