Skip to content

← Memory, Copies and Slow Code step 19 of 38

Medium Framework

Measure Memory Growth and Always Stop Tracing

Write find_growth(fn, *, iterations), which measures how much traced memory grows while fn runs iterations times. iterations is zero or more. A positive result means traced memory grew during the measurement. It does not prove a leak, and it is not every byte that fn allocated.

kept: list[bytes] = []
find_growth(lambda: kept.append(bytes(10_000)), iterations=50)
# about 502_700, for 50 more 10,000-byte objects plus a little overhead

What to write

Follow these steps in order:

  1. If tracing is already running, raise RuntimeError at once. Do not call fn, and do not start, stop or clear tracing, because that session belongs to someone else.
  2. Start tracing. Call fn() once to warm up, then take the first snapshot.
  3. Call fn() exactly iterations more times, then take the second snapshot.
  4. Return the sum of every size_diff in after.compare_to(before, "lineno"), negative values included.
  5. In a finally block, stop the tracing you started, so that it stops even when fn or a snapshot raises. Let that exception reach the caller unchanged.

With zero iterations the warm-up still runs once. The result does not have to be zero, because taking snapshots allocates memory too. Do not round a negative total up to zero, and do not force any particular number. You can assume nothing else starts or stops tracing while find_growth runs. Your code must pass mypy --strict.

What the driver does

Keep the workloads and solve as they are:

Workload What it does
leaks Adds a list of 1,000 items to the global SINK, which keeps it
churns Builds a list and clears it, without keeping it
explodes Raises ValueError, so the warm-up call fails

solve empties SINK, measures with N iterations, empties SINK again and measures with 2N. The second run's lists stay in SINK until the next call to solve. Tracing is off when solve starts.

The report contains:

  • grew: whether the first total is more than 100,000 bytes.
  • scaled: whether grew is true and the second total is at least 1.5 times the first.
  • raised: "ValueError" if a workload raised it, otherwise "".
  • tracing_stopped: whether tracing is off at the end.

These thresholds suit these small workloads. They are not rules for spotting a leak in real code.