Skip to content

Commit 9e00e80

Browse files
authored
bpo-35053: Enhance tracemalloc to trace free lists (GH-10063)
tracemalloc now tries to update the traceback when an object is reused from a "free list" (optimization for faster object creation, used by the builtin list type for example). Changes: * Add _PyTraceMalloc_NewReference() function which tries to update the Python traceback of a Python object. * _Py_NewReference() now calls _PyTraceMalloc_NewReference(). * Add an unit test.
1 parent d7c3e5f commit 9e00e80

File tree

7 files changed

+157
-51
lines changed

7 files changed

+157
-51
lines changed

Include/object.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,9 @@ PyAPI_FUNC(void) _Py_AddToAllObjects(PyObject *, int force);
776776
* inline.
777777
*/
778778
#define _Py_NewReference(op) ( \
779+
(_Py_tracemalloc_config.tracing \
780+
? _PyTraceMalloc_NewReference(op) \
781+
: 0), \
779782
_Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \
780783
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
781784
Py_REFCNT(op) = 1)

Include/pymem.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ PyAPI_FUNC(int) PyTraceMalloc_Track(
3636
uintptr_t ptr,
3737
size_t size);
3838

39+
/* Update the Python traceback of an object.
40+
This function can be used when a memory block is reused from a free list. */
41+
PyAPI_FUNC(int) _PyTraceMalloc_NewReference(PyObject *op);
42+
3943
/* Untrack an allocated memory block in the tracemalloc module.
4044
Do nothing if the block was not tracked.
4145
@@ -239,6 +243,40 @@ PyAPI_FUNC(int) _PyMem_SetDefaultAllocator(
239243
PyMemAllocatorEx *old_alloc);
240244
#endif
241245

246+
247+
/* bpo-35053: expose _Py_tracemalloc_config for performance:
248+
_Py_NewReference() needs an efficient check to test if tracemalloc is
249+
tracing. */
250+
struct _PyTraceMalloc_Config {
251+
/* Module initialized?
252+
Variable protected by the GIL */
253+
enum {
254+
TRACEMALLOC_NOT_INITIALIZED,
255+
TRACEMALLOC_INITIALIZED,
256+
TRACEMALLOC_FINALIZED
257+
} initialized;
258+
259+
/* Is tracemalloc tracing memory allocations?
260+
Variable protected by the GIL */
261+
int tracing;
262+
263+
/* limit of the number of frames in a traceback, 1 by default.
264+
Variable protected by the GIL. */
265+
int max_nframe;
266+
267+
/* use domain in trace key?
268+
Variable protected by the GIL. */
269+
int use_domain;
270+
};
271+
272+
PyAPI_DATA(struct _PyTraceMalloc_Config) _Py_tracemalloc_config;
273+
274+
#define _PyTraceMalloc_Config_INIT \
275+
{.initialized = TRACEMALLOC_NOT_INITIALIZED, \
276+
.tracing = 0, \
277+
.max_nframe = 1, \
278+
.use_domain = 0}
279+
242280
#ifdef __cplusplus
243281
}
244282
#endif

Lib/test/test_tracemalloc.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ def test_get_object_traceback(self):
111111
traceback = tracemalloc.get_object_traceback(obj)
112112
self.assertEqual(traceback, obj_traceback)
113113

114+
def test_new_reference(self):
115+
tracemalloc.clear_traces()
116+
# gc.collect() indirectly calls PyList_ClearFreeList()
117+
support.gc_collect()
118+
119+
# Create a list and "destroy it": put it in the PyListObject free list
120+
obj = []
121+
obj = None
122+
123+
# Create a list which should reuse the previously created empty list
124+
obj = []
125+
126+
nframe = tracemalloc.get_traceback_limit()
127+
frames = get_frames(nframe, -3)
128+
obj_traceback = tracemalloc.Traceback(frames)
129+
130+
traceback = tracemalloc.get_object_traceback(obj)
131+
self.assertIsNotNone(traceback)
132+
self.assertEqual(traceback, obj_traceback)
133+
114134
def test_set_traceback_limit(self):
115135
obj_size = 10
116136

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tracemalloc now tries to update the traceback when an object is reused from a
2+
"free list" (optimization for faster object creation, used by the builtin list
3+
type for example).

0 commit comments

Comments
 (0)