pymemtrace.debug_malloc_stats
¶
This is a wrapper around sys._debugmallocstats which writes to C stderr. We capture this and can diff two calls to sys._debugmallocatats.
sys._debugmallocatats Implementation (Python 3.9)¶
sys__debugmallocstats_impl(PyObject *module)
Calls:
_PyObject_DebugMallocStats(stderr))
Then:
_PyObject_DebugTypeStats(stderr);
Memory Usage by the Python Object Allocator¶
_PyObject_DebugMallocStats is defined in Objects/obmalloc.c:
Which dumps out the arenas/pools/blocks.
Memory Usage by Type¶
_PyObject_DebugTypeStats
is defined in Objects/object.c
This calls:
_PyDict_DebugMallocStats(out); // in Objects/dictobject.c, just calls _PyDebugAllocatorStats in Objects/obmalloc.c
_PyFloat_DebugMallocStats(out); // in Objects/dictobject.c, just calls _PyDebugAllocatorStats in Objects/obmalloc.c
_PyFrame_DebugMallocStats(out); // etc.
_PyList_DebugMallocStats(out);
_PyTuple_DebugMallocStats(out); // in Objects/tupleobject.c, calls _PyDebugAllocatorStats in Objects/obmalloc.c for (i = 1; i < PyTuple_MAXSAVESIZE; i++)
Note that only dict, float, frame, list, tuple are reported.
- class pymemtrace.debug_malloc_stats.DebugMallocArenas(debug_malloc: bytes)[source]¶
Decomposes this:
# arenas allocated total = 2,033 # arenas reclaimed = 2,015 # arenas highwater mark = 18 # arenas allocated current = 18 18 arenas * 262144 bytes/arena = 4,718,592
Into values for: ntimes_arena_allocated, narenas, narenas_highwater, ARENA_SIZE.
Infers: arenas_reclaimed, arenas_total.
From Object/obmalloc.c:
(void)printone(out, "# arenas allocated total", ntimes_arena_allocated); (void)printone(out, "# arenas reclaimed", ntimes_arena_allocated - narenas); (void)printone(out, "# arenas highwater mark", narenas_highwater); (void)printone(out, "# arenas allocated current", narenas); /* Total number of times malloc() called to allocate an arena. */ static size_t ntimes_arena_allocated = 0; // b'# arenas allocated total = 2,033' /* High water mark (max value ever seen) for narenas_currently_allocated. */ static size_t narenas_highwater = 0; // b'# arenas highwater mark = 18' /* # of arenas actually allocated. */ size_t narenas = 0; // b'# arenas allocated current = 18' PyOS_snprintf(buf, sizeof(buf), "%" PY_FORMAT_SIZE_T "u arenas * %d bytes/arena", narenas, ARENA_SIZE); (void)printone(out, buf, narenas * ARENA_SIZE); // b'18 arenas * 262144 bytes/arena = 4,718,592' // Simple calculation: 18 * 262144 = 4718592
- __init__(debug_malloc: bytes)[source]¶
Constructor, decomposes this:
# arenas allocated total -> self.ntimes_arena_allocated # arenas reclaimed -> self.arenas_reclaimed # arenas highwater mark -> self.narenas_highwater # arenas allocated current -> self.narenas 18 arenas * 262144 bytes/arena -> self.narenas * self.arena_size = self.arenas_total
- __weakref__¶
list of weak references to the object (if defined)
- class pymemtrace.debug_malloc_stats.DebugMallocPoolsBlocks(debug_malloc: bytes)[source]¶
Decomposes this:
# bytes in allocated blocks = 4,280,848 # bytes in available blocks = 70,368 63 unused pools * 4096 bytes = 258,048 # bytes lost to pool headers = 52,272 # bytes lost to quantization = 57,056 # bytes lost to arena alignment = 0 Total = 4,718,592
From Object/obmalloc.c:
total = printone(out, "# bytes in allocated blocks", allocated_bytes); total += printone(out, "# bytes in available blocks", available_bytes); PyOS_snprintf(buf, sizeof(buf), "%u unused pools * %d bytes", numfreepools, POOL_SIZE); total += printone(out, buf, (size_t)numfreepools * POOL_SIZE); total += printone(out, "# bytes lost to pool headers", pool_header_bytes); total += printone(out, "# bytes lost to quantization", quantization); total += printone(out, "# bytes lost to arena alignment", arena_alignment); (void)printone(out, "Total", total);
Extracts: allocated_bytes, available_bytes, numfreepools, POOL_SIZE, pool_header_bytes, quantization, arena_alignment.
Infers: unused_pool_total, TOTAL.
- __weakref__¶
list of weak references to the object (if defined)
- class pymemtrace.debug_malloc_stats.DebugMallocStat(block_class: int, size: int, num_pools: int, blocks_in_use: int, avail_blocks: int)[source]¶
Represents a single line in the malloc stats section. For example:
class size num pools blocks in use avail blocks ----- ---- --------- ------------- ------------ 0 16 2 297 209
Nomenclature is from
_PyObject_DebugMallocStats(stderr))
inObjects/obmalloc.c
. Typical implementation:for (i = 0; i < numclasses; ++i) { size_t p = numpools[i]; size_t b = numblocks[i]; size_t f = numfreeblocks[i]; uint size = INDEX2SIZE(i); if (p == 0) { assert(b == 0 && f == 0); continue; } fprintf(out, "%5u %6u " "%11" PY_FORMAT_SIZE_T "u " "%15" PY_FORMAT_SIZE_T "u " "%13" PY_FORMAT_SIZE_T "u\n", i, size, p, b, f); allocated_bytes += b * size; available_bytes += f * size; pool_header_bytes += p * POOL_OVERHEAD; quantization += p * ((POOL_SIZE - POOL_OVERHEAD) % size); } fputc('\n', out);
- __getnewargs__()¶
Return self as a plain tuple. Used by copy and pickle.
- static __new__(_cls, block_class: int, size: int, num_pools: int, blocks_in_use: int, avail_blocks: int)¶
Create new instance of DebugMallocStat(block_class, size, num_pools, blocks_in_use, avail_blocks)
- _asdict()¶
Return a new dict which maps field names to their values.
- classmethod _make(iterable)¶
Make a new DebugMallocStat object from a sequence or iterable
- _replace(**kwds)¶
Return a new DebugMallocStat object replacing specified fields with new values
- avail_blocks: int¶
Alias for field number 4
- block_class: int¶
Alias for field number 0
- blocks_in_use: int¶
Alias for field number 3
- num_pools: int¶
Alias for field number 2
- size: int¶
Alias for field number 1
- class pymemtrace.debug_malloc_stats.DebugTypeStat(free_count: int, object_type: str, bytes_each: int, bytes_total: int)[source]¶
Represents a single line from
sys._debugmallocstats
.Decomposed from a line such as:
4 free PyCFunctionObjects * 56 bytes each = 224
See
_PyObject_DebugTypeStats(stderr);
inObjects/obmalloc.c
- __getnewargs__()¶
Return self as a plain tuple. Used by copy and pickle.
- static __new__(_cls, free_count: int, object_type: str, bytes_each: int, bytes_total: int)¶
Create new instance of DebugTypeStat(free_count, object_type, bytes_each, bytes_total)
- __repr__()[source]¶
Returns a string of the form of these lines:
4 free PyCFunctionObjects * 56 bytes each = 224 9 free PyDictObjects * 48 bytes each = 432 5 free PyFloatObjects * 24 bytes each = 120 0 free PyFrameObjects * 368 bytes each = 0 80 free PyListObjects * 40 bytes each = 3,200 8 free PyMethodObjects * 48 bytes each = 384 7 free 1-sized PyTupleObjects * 32 bytes each = 224 52 free 2-sized PyTupleObjects * 40 bytes each = 2,080 1 free 3-sized PyTupleObjects * 48 bytes each = 48 0 free 10-sized PyTupleObjects * 104 bytes each = 0
- _asdict()¶
Return a new dict which maps field names to their values.
- classmethod _make(iterable)¶
Make a new DebugTypeStat object from a sequence or iterable
- _replace(**kwds)¶
Return a new DebugTypeStat object replacing specified fields with new values
- bytes_each: int¶
Alias for field number 2
- bytes_total: int¶
Alias for field number 3
- free_count: int¶
Alias for field number 0
- object_type: str¶
Alias for field number 1
- class pymemtrace.debug_malloc_stats.DiffSysDebugMallocStats[source]¶
Context manager that compares two snapshots of
sys._getdebugmallocstats()
and can provide a diff between them.- __exit__(exc_type, exc_val, exc_tb)[source]¶
Exits the context manager taking a snapshot of
sys._getdebugmallocstats()
.
- __weakref__¶
list of weak references to the object (if defined)
- pymemtrace.debug_malloc_stats.POOL_OVERHEAD = 48¶
This value is initially approximate. In
Object/obmalloc.c
:#define POOL_OVERHEAD _Py_SIZE_ROUND_UP(sizeof(struct pool_header), ALIGNMENT)
We can calculate this from the sum of num_pools divided into
'# bytes lost to pool headers'
. This is done whenever aSysDebugMallocStats
is created.
- pymemtrace.debug_malloc_stats.POOL_SIZE = 4096¶
In
Object/obmalloc.c
:#define POOL_SIZE SYSTEM_PAGE_SIZE /* must be 2^N */
- pymemtrace.debug_malloc_stats.RE_DEBUG_MALLOC_ARENAS_SUMMARY_LINE = re.compile(b'^(\\d+) arenas \\* (\\d+) bytes/arena\\s+=\\s+(.+)$')¶
Matches:
b'18 arenas * 262144 bytes/arena = 4,718,592'
Decomposed to extract the two integers, the total is computed.
- pymemtrace.debug_malloc_stats.RE_DEBUG_MALLOC_HEADER_LINE = re.compile(b'^Small block threshold = (\\d+), in (\\d+) size classes\\.$')¶
Matches:
b'Small block threshold = 512, in 32 size classes.'
Decomposed to extract the two integers.
- pymemtrace.debug_malloc_stats.RE_DEBUG_MALLOC_POOLS_SUMMARY_LINE = re.compile(b'^(\\d+) unused pools \\* (\\d+) bytes\\s+=\\s+(.+)$')¶
Matches:
b'63 unused pools * 4096 bytes = 258,048'
Decomposed to extract the two integers, the total is computed.
- pymemtrace.debug_malloc_stats.RE_DEBUG_MALLOC_STATS_LINE = re.compile(b'^\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)$')¶
Matches:
b' 0 16 2 294 212'
Decomposed to extract the five integers.
- pymemtrace.debug_malloc_stats.RE_DEBUG_MALLOC_TYPE_LINE = re.compile(b'^\\s*(\\d+) free (.+?) \\* (\\d+) bytes each =\\s+(.+)$')¶
Matches:
b' 34 free 2-sized PyTupleObjects * 40 bytes each = 1,360'
NOTE: commas in last value caused by printone() in Object/obmalloc.c printone is used in many places where there is message = value such as memory pool totals and type memory information.
- class pymemtrace.debug_malloc_stats.SysDebugMallocStats(debug_malloc: bytes = b'')[source]¶
This decomposes the output of
sys._debugmallocstats
into these areas:A list of malloc stats showing the pools and blocks.
Descriptions of arenas.
Descriptions of pools and blocks.
A list of malloc usage by (some) types.
This class takes a snapshot of the debug malloc stats from
sys._debugmallocstats
. Importantly it can identify the difference between two snapshots.- __init__(debug_malloc: bytes = b'')[source]¶
Constructor, this optionally takes a bytes object for testing. If nothing supplied this gets the bytes object from sys._debugmallocstats.
- __weakref__¶
list of weak references to the object (if defined)
- type_stat(object_type: bytes) DebugTypeStat [source]¶
Return the DebugTypeStat for the named object type. May raise an KeyError if the object_type doe not exist.
- pymemtrace.debug_malloc_stats.diff_debug_malloc_stat(a: DebugMallocStat, b: DebugMallocStat) str [source]¶
Takes two DebugMallocStat objects and returns a string with the difference. The string is of similar format to the input from
sys._debugmallocstats
.
- pymemtrace.debug_malloc_stats.diff_debug_type_stat(a: DebugTypeStat, b: DebugTypeStat) str [source]¶
Takes two DebugMallocStat objects and returns a string with the difference. The string is of similar format to the input from
sys._debugmallocstats
.
- pymemtrace.debug_malloc_stats.diff_debugmallocstats(a_stats: SysDebugMallocStats, b_stats: SysDebugMallocStats)[source]¶
This takes two SysDebugMallocStats objects and identifies what is different between them. The diff is a list of lines of identical form to
sys._debugmallocstats()
with ‘+’ or ‘-’ where appropriate. Lines that are the same are omitted.