[Python-Dev] RFC: PEP 454: Add a new tracemalloc module

Victor Stinner victor.stinner at gmail.com
Wed Sep 4 01:56:21 CEST 2013


> ``get_object_trace(obj)`` function:
>
>     Get the trace of a Python object *obj* as a ``trace`` instance.
>
>     Return ``None`` if the tracemalloc module did not save the location
>     when the object was allocated, for example if the module was
>     disabled.

This function and get_traces() can be reused by other debug tools like
Heapy and objgraph to add where objects were allocated.

> ``get_stats()`` function:
>
>     Get statistics on Python memory allocations per Python filename and
>     per Python line number.
>
>     Return a dictionary
>     ``{filename: str -> {line_number: int -> stats: line_stat}}``
>     where *stats* in a ``line_stat`` instance. *filename* and
>     *line_number* can be ``None``.
>
>     Return an empty dictionary if the tracemalloc module is disabled.
>
> ``get_traces(obj)`` function:
>
>    Get all traces of a Python memory allocations.
>    Return a dictionary ``{pointer: int -> trace}`` where *trace*
>    is a ``trace`` instance.
>
>    Return an empty dictionary if the ``tracemalloc`` module is disabled.

get_stats() can computed from get_traces(), example:
-----
import pprint, tracemalloc

traces = tracemalloc.get_traces()
stats = {}
for trace in traces.values():
    if trace.filename not in stats:
        stats[trace.filename] = line_stats = {}
    else:
        line_stats = stats[trace.filename]
    if trace.lineno not in line_stats:
        line_stats[trace.lineno] = line_stat = tracemalloc.line_stat((0, 0))
        size = trace.size
        count = 1
    else:
        line_stat = line_stats[trace.lineno]
        size = line_stat.size + trace.size
        count = line_stat.count + 1
    line_stats[trace.lineno] = tracemalloc.line_stat((size, count))

pprint.pprint(stats)
-----

The problem is the efficiency. At startup, Python already allocated
more than 20,000 memory blocks:

$ ./python -X tracemalloc -c 'import tracemalloc;
print(len(tracemalloc.get_traces()))'
21704

At the end of the Python test suite, Python allocated more than
500,000 memory blocks.

Storing all these traces in a snapshot eats a lot of memory, disk
space and uses CPU to build the statistics.

> ``start_timer(delay: int, func: callable, args: tuple=(), kwargs:
> dict={})`` function:
>
>     Start a timer calling ``func(*args, **kwargs)`` every *delay*
>     seconds. (...)
>
>     If ``start_timer()`` is called twice, previous parameters are
>     replaced.  The timer has a resolution of 1 second.
>
>     ``start_timer()`` is used by ``DisplayTop`` and ``TakeSnapshot`` to
>     run regulary a task.

So DisplayTop and TakeSnapshot cannot be used at the same time. It
would be convinient to be able to register more than one function.
What do you think?

> ``trace`` class:
>     This class represents debug information of an allocated memory block.
>
> ``size`` attribute:
>     Size in bytes of the memory block.
> ``filename`` attribute:
>     Name of the Python script where the memory block was allocated,
>     ``None`` if unknown.
> ``lineno`` attribute:
>     Line number where the memory block was allocated, ``None`` if
>     unknown.

I though twice and it would be posible to store more than 1 frame per
trace instance, to be able to rebuild a (partial) Python traceback.
The hook on the memory allocator has access to the chain of Python
frames. The API should be changed to support such enhancement.

> ``DisplayTop(count: int=10, file=sys.stdout)`` class:
>     Display the list of the *count* biggest memory allocations into
>     *file*.
> (...)
> ``group_per_file`` attribute:
>
>     If ``True``, group memory allocations per Python filename. If
>     ``False`` (default value), group allocation per Python line number.

This attribute is very important. We may add it to the constructor.

By the way, the self.stream attribute is not documented.

> Snapshot class
> --------------
>
> ``Snapshot()`` class:
>
>     Snapshot of Python memory allocations.
>
>     Use ``TakeSnapshot`` to take regulary snapshots.
>
> ``create(user_data_callback=None)`` method:
>
>     Take a snapshot. If *user_data_callback* is specified, it must be a
>     callable object returning a list of
>     ``(title: str, format: str, value: int)``.
>     *format* must be ``'size'``. The list must always have the same
>     length and the same order to be able to compute differences between
>     values.
>
>     Example: ``[('Video memory', 'size', 234902)]``.

(Oops, create() is a class method, not a method.)

Having to call a class method to build an instance of a class is
surprising. But I didn't find a way to implement the load() class
method otherwise.

The user_data_callback API can be improved. The "format must be size"
is not very convinient.

Victor


More information about the Python-Dev mailing list