Skip to content

Commit c87b0e4

Browse files
authored
GH-124284: Add stats for refcount operations on immortal objects (GH-124288)
1 parent 6203ef3 commit c87b0e4

File tree

7 files changed

+45
-10
lines changed

7 files changed

+45
-10
lines changed

Include/cpython/pystats.h

+8
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ typedef struct _object_stats {
7070
uint64_t decrefs;
7171
uint64_t interpreter_increfs;
7272
uint64_t interpreter_decrefs;
73+
uint64_t immortal_increfs;
74+
uint64_t immortal_decrefs;
75+
uint64_t interpreter_immortal_increfs;
76+
uint64_t interpreter_immortal_decrefs;
7377
uint64_t allocations;
7478
uint64_t allocations512;
7579
uint64_t allocations4k;
@@ -163,7 +167,11 @@ PyAPI_DATA(PyStats*) _Py_stats;
163167
#ifdef _PY_INTERPRETER
164168
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_increfs++; } while (0)
165169
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_decrefs++; } while (0)
170+
# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_increfs++; } while (0)
171+
# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_decrefs++; } while (0)
166172
#else
167173
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.increfs++; } while (0)
168174
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.decrefs++; } while (0)
175+
# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_increfs++; } while (0)
176+
# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_decrefs++; } while (0)
169177
#endif

Include/internal/pycore_object.h

+5
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ static inline void
214214
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
215215
{
216216
if (_Py_IsImmortal(op)) {
217+
_Py_DECREF_IMMORTAL_STAT_INC();
217218
return;
218219
}
219220
_Py_DECREF_STAT_INC();
@@ -235,6 +236,7 @@ static inline void
235236
_Py_DECREF_NO_DEALLOC(PyObject *op)
236237
{
237238
if (_Py_IsImmortal(op)) {
239+
_Py_DECREF_IMMORTAL_STAT_INC();
238240
return;
239241
}
240242
_Py_DECREF_STAT_INC();
@@ -315,6 +317,7 @@ _Py_INCREF_TYPE(PyTypeObject *type)
315317
{
316318
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
317319
assert(_Py_IsImmortalLoose(type));
320+
_Py_INCREF_IMMORTAL_STAT_INC();
318321
return;
319322
}
320323

@@ -355,6 +358,7 @@ _Py_DECREF_TYPE(PyTypeObject *type)
355358
{
356359
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
357360
assert(_Py_IsImmortalLoose(type));
361+
_Py_DECREF_IMMORTAL_STAT_INC();
358362
return;
359363
}
360364

@@ -511,6 +515,7 @@ _Py_TryIncrefFast(PyObject *op) {
511515
local += 1;
512516
if (local == 0) {
513517
// immortal
518+
_Py_INCREF_IMMORTAL_STAT_INC();
514519
return 1;
515520
}
516521
if (_Py_IsOwnedByCurrentThread(op)) {

Include/pystats.h

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ extern "C" {
1818
#else
1919
# define _Py_INCREF_STAT_INC() ((void)0)
2020
# define _Py_DECREF_STAT_INC() ((void)0)
21+
# define _Py_INCREF_IMMORTAL_STAT_INC() ((void)0)
22+
# define _Py_DECREF_IMMORTAL_STAT_INC() ((void)0)
2123
#endif // !Py_STATS
2224

2325
#ifdef __cplusplus

Include/refcount.h

+7
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
227227
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
228228
uint32_t new_local = local + 1;
229229
if (new_local == 0) {
230+
_Py_INCREF_IMMORTAL_STAT_INC();
230231
// local is equal to _Py_IMMORTAL_REFCNT: do nothing
231232
return;
232233
}
@@ -241,6 +242,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
241242
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
242243
PY_UINT32_T new_refcnt = cur_refcnt + 1;
243244
if (new_refcnt == 0) {
245+
_Py_INCREF_IMMORTAL_STAT_INC();
244246
// cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal,
245247
// do nothing
246248
return;
@@ -249,6 +251,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
249251
#else
250252
// Explicitly check immortality against the immortal value
251253
if (_Py_IsImmortal(op)) {
254+
_Py_INCREF_IMMORTAL_STAT_INC();
252255
return;
253256
}
254257
op->ob_refcnt++;
@@ -295,6 +298,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
295298
{
296299
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
297300
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
301+
_Py_DECREF_IMMORTAL_STAT_INC();
298302
return;
299303
}
300304
_Py_DECREF_STAT_INC();
@@ -320,6 +324,7 @@ static inline void Py_DECREF(PyObject *op)
320324
{
321325
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
322326
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
327+
_Py_DECREF_IMMORTAL_STAT_INC();
323328
return;
324329
}
325330
_Py_DECREF_STAT_INC();
@@ -343,6 +348,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
343348
_Py_NegativeRefcount(filename, lineno, op);
344349
}
345350
if (_Py_IsImmortal(op)) {
351+
_Py_DECREF_IMMORTAL_STAT_INC();
346352
return;
347353
}
348354
_Py_DECREF_STAT_INC();
@@ -359,6 +365,7 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
359365
// Non-limited C API and limited C API for Python 3.9 and older access
360366
// directly PyObject.ob_refcnt.
361367
if (_Py_IsImmortal(op)) {
368+
_Py_DECREF_IMMORTAL_STAT_INC();
362369
return;
363370
}
364371
_Py_DECREF_STAT_INC();

Python/ceval.c

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
do { \
8080
PyObject *op = _PyObject_CAST(arg); \
8181
if (_Py_IsImmortal(op)) { \
82+
_Py_DECREF_IMMORTAL_STAT_INC(); \
8283
break; \
8384
} \
8485
_Py_DECREF_STAT_INC(); \
@@ -93,6 +94,7 @@
9394
do { \
9495
PyObject *op = _PyObject_CAST(arg); \
9596
if (_Py_IsImmortal(op)) { \
97+
_Py_DECREF_IMMORTAL_STAT_INC(); \
9698
break; \
9799
} \
98100
_Py_DECREF_STAT_INC(); \
@@ -110,6 +112,7 @@
110112
PyObject *op = _PyObject_CAST(arg); \
111113
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); \
112114
if (local == _Py_IMMORTAL_REFCNT_LOCAL) { \
115+
_Py_DECREF_IMMORTAL_STAT_INC(); \
113116
break; \
114117
} \
115118
_Py_DECREF_STAT_INC(); \

Python/specialize.c

+8-4
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,14 @@ print_object_stats(FILE *out, ObjectStats *stats)
205205
fprintf(out, "Object allocations over 4 kbytes: %" PRIu64 "\n", stats->allocations_big);
206206
fprintf(out, "Object frees: %" PRIu64 "\n", stats->frees);
207207
fprintf(out, "Object inline values: %" PRIu64 "\n", stats->inline_values);
208-
fprintf(out, "Object interpreter increfs: %" PRIu64 "\n", stats->interpreter_increfs);
209-
fprintf(out, "Object interpreter decrefs: %" PRIu64 "\n", stats->interpreter_decrefs);
210-
fprintf(out, "Object increfs: %" PRIu64 "\n", stats->increfs);
211-
fprintf(out, "Object decrefs: %" PRIu64 "\n", stats->decrefs);
208+
fprintf(out, "Object interpreter mortal increfs: %" PRIu64 "\n", stats->interpreter_increfs);
209+
fprintf(out, "Object interpreter mortal decrefs: %" PRIu64 "\n", stats->interpreter_decrefs);
210+
fprintf(out, "Object mortal increfs: %" PRIu64 "\n", stats->increfs);
211+
fprintf(out, "Object mortal decrefs: %" PRIu64 "\n", stats->decrefs);
212+
fprintf(out, "Object interpreter immortal increfs: %" PRIu64 "\n", stats->interpreter_immortal_increfs);
213+
fprintf(out, "Object interpreter immortal decrefs: %" PRIu64 "\n", stats->interpreter_immortal_decrefs);
214+
fprintf(out, "Object immortal increfs: %" PRIu64 "\n", stats->immortal_increfs);
215+
fprintf(out, "Object immortal decrefs: %" PRIu64 "\n", stats->immortal_decrefs);
212216
fprintf(out, "Object materialize dict (on request): %" PRIu64 "\n", stats->dict_materialized_on_request);
213217
fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key);
214218
fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big);

Tools/scripts/summarize_stats.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -398,12 +398,18 @@ def get_object_stats(self) -> dict[str, tuple[int, int]]:
398398
total_allocations = self._data.get("Object allocations", 0) + self._data.get(
399399
"Object allocations from freelist", 0
400400
)
401-
total_increfs = self._data.get(
402-
"Object interpreter increfs", 0
403-
) + self._data.get("Object increfs", 0)
404-
total_decrefs = self._data.get(
405-
"Object interpreter decrefs", 0
406-
) + self._data.get("Object decrefs", 0)
401+
total_increfs = (
402+
self._data.get("Object interpreter mortal increfs", 0) +
403+
self._data.get("Object mortal increfs", 0) +
404+
self._data.get("Object interpreter immortal increfs", 0) +
405+
self._data.get("Object immortal increfs", 0)
406+
)
407+
total_decrefs = (
408+
self._data.get("Object interpreter mortal decrefs", 0) +
409+
self._data.get("Object mortal decrefs", 0) +
410+
self._data.get("Object interpreter immortal decrefs", 0) +
411+
self._data.get("Object immortal decrefs", 0)
412+
)
407413

408414
result = {}
409415
for key, value in self._data.items():

0 commit comments

Comments
 (0)