Skip to content

GH-124284: Add stats for refcount operations on immortal objects #124288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Include/cpython/pystats.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ typedef struct _object_stats {
uint64_t decrefs;
uint64_t interpreter_increfs;
uint64_t interpreter_decrefs;
uint64_t immortal_increfs;
uint64_t immortal_decrefs;
uint64_t interpreter_immortal_increfs;
uint64_t interpreter_immortal_decrefs;
uint64_t allocations;
uint64_t allocations512;
uint64_t allocations4k;
Expand Down Expand Up @@ -163,7 +167,11 @@ PyAPI_DATA(PyStats*) _Py_stats;
#ifdef _PY_INTERPRETER
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_increfs++; } while (0)
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_decrefs++; } while (0)
# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_increfs++; } while (0)
# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_immortal_decrefs++; } while (0)
#else
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.increfs++; } while (0)
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.decrefs++; } while (0)
# define _Py_INCREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_increfs++; } while (0)
# define _Py_DECREF_IMMORTAL_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.immortal_decrefs++; } while (0)
#endif
5 changes: 5 additions & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ static inline void
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
{
if (_Py_IsImmortal(op)) {
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
Expand All @@ -235,6 +236,7 @@ static inline void
_Py_DECREF_NO_DEALLOC(PyObject *op)
{
if (_Py_IsImmortal(op)) {
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
Expand Down Expand Up @@ -315,6 +317,7 @@ _Py_INCREF_TYPE(PyTypeObject *type)
{
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
assert(_Py_IsImmortalLoose(type));
_Py_INCREF_IMMORTAL_STAT_INC();
return;
}

Expand Down Expand Up @@ -355,6 +358,7 @@ _Py_DECREF_TYPE(PyTypeObject *type)
{
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
assert(_Py_IsImmortalLoose(type));
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}

Expand Down Expand Up @@ -511,6 +515,7 @@ _Py_TryIncrefFast(PyObject *op) {
local += 1;
if (local == 0) {
// immortal
_Py_INCREF_IMMORTAL_STAT_INC();
return 1;
}
if (_Py_IsOwnedByCurrentThread(op)) {
Expand Down
2 changes: 2 additions & 0 deletions Include/pystats.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ extern "C" {
#else
# define _Py_INCREF_STAT_INC() ((void)0)
# define _Py_DECREF_STAT_INC() ((void)0)
# define _Py_INCREF_IMMORTAL_STAT_INC() ((void)0)
# define _Py_DECREF_IMMORTAL_STAT_INC() ((void)0)
#endif // !Py_STATS

#ifdef __cplusplus
Expand Down
7 changes: 7 additions & 0 deletions Include/refcount.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
uint32_t new_local = local + 1;
if (new_local == 0) {
_Py_INCREF_IMMORTAL_STAT_INC();
// local is equal to _Py_IMMORTAL_REFCNT: do nothing
return;
}
Expand All @@ -241,6 +242,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
PY_UINT32_T new_refcnt = cur_refcnt + 1;
if (new_refcnt == 0) {
_Py_INCREF_IMMORTAL_STAT_INC();
// cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal,
// do nothing
return;
Expand All @@ -249,6 +251,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
#else
// Explicitly check immortality against the immortal value
if (_Py_IsImmortal(op)) {
_Py_INCREF_IMMORTAL_STAT_INC();
return;
}
op->ob_refcnt++;
Expand Down Expand Up @@ -295,6 +298,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
Expand All @@ -320,6 +324,7 @@ static inline void Py_DECREF(PyObject *op)
{
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
Expand All @@ -343,6 +348,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
_Py_NegativeRefcount(filename, lineno, op);
}
if (_Py_IsImmortal(op)) {
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
Expand All @@ -359,6 +365,7 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
// Non-limited C API and limited C API for Python 3.9 and older access
// directly PyObject.ob_refcnt.
if (_Py_IsImmortal(op)) {
_Py_DECREF_IMMORTAL_STAT_INC();
return;
}
_Py_DECREF_STAT_INC();
Expand Down
3 changes: 3 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
do { \
PyObject *op = _PyObject_CAST(arg); \
if (_Py_IsImmortal(op)) { \
_Py_DECREF_IMMORTAL_STAT_INC(); \
break; \
} \
_Py_DECREF_STAT_INC(); \
Expand All @@ -93,6 +94,7 @@
do { \
PyObject *op = _PyObject_CAST(arg); \
if (_Py_IsImmortal(op)) { \
_Py_DECREF_IMMORTAL_STAT_INC(); \
break; \
} \
_Py_DECREF_STAT_INC(); \
Expand All @@ -110,6 +112,7 @@
PyObject *op = _PyObject_CAST(arg); \
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); \
if (local == _Py_IMMORTAL_REFCNT_LOCAL) { \
_Py_DECREF_IMMORTAL_STAT_INC(); \
break; \
} \
_Py_DECREF_STAT_INC(); \
Expand Down
12 changes: 8 additions & 4 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,14 @@ print_object_stats(FILE *out, ObjectStats *stats)
fprintf(out, "Object allocations over 4 kbytes: %" PRIu64 "\n", stats->allocations_big);
fprintf(out, "Object frees: %" PRIu64 "\n", stats->frees);
fprintf(out, "Object inline values: %" PRIu64 "\n", stats->inline_values);
fprintf(out, "Object interpreter increfs: %" PRIu64 "\n", stats->interpreter_increfs);
fprintf(out, "Object interpreter decrefs: %" PRIu64 "\n", stats->interpreter_decrefs);
fprintf(out, "Object increfs: %" PRIu64 "\n", stats->increfs);
fprintf(out, "Object decrefs: %" PRIu64 "\n", stats->decrefs);
fprintf(out, "Object interpreter mortal increfs: %" PRIu64 "\n", stats->interpreter_increfs);
fprintf(out, "Object interpreter mortal decrefs: %" PRIu64 "\n", stats->interpreter_decrefs);
fprintf(out, "Object mortal increfs: %" PRIu64 "\n", stats->increfs);
fprintf(out, "Object mortal decrefs: %" PRIu64 "\n", stats->decrefs);
fprintf(out, "Object interpreter immortal increfs: %" PRIu64 "\n", stats->interpreter_immortal_increfs);
fprintf(out, "Object interpreter immortal decrefs: %" PRIu64 "\n", stats->interpreter_immortal_decrefs);
fprintf(out, "Object immortal increfs: %" PRIu64 "\n", stats->immortal_increfs);
fprintf(out, "Object immortal decrefs: %" PRIu64 "\n", stats->immortal_decrefs);
fprintf(out, "Object materialize dict (on request): %" PRIu64 "\n", stats->dict_materialized_on_request);
fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key);
fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big);
Expand Down
18 changes: 12 additions & 6 deletions Tools/scripts/summarize_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,12 +398,18 @@ def get_object_stats(self) -> dict[str, tuple[int, int]]:
total_allocations = self._data.get("Object allocations", 0) + self._data.get(
"Object allocations from freelist", 0
)
total_increfs = self._data.get(
"Object interpreter increfs", 0
) + self._data.get("Object increfs", 0)
total_decrefs = self._data.get(
"Object interpreter decrefs", 0
) + self._data.get("Object decrefs", 0)
total_increfs = (
self._data.get("Object interpreter mortal increfs", 0) +
self._data.get("Object mortal increfs", 0) +
self._data.get("Object interpreter immortal increfs", 0) +
self._data.get("Object immortal increfs", 0)
)
total_decrefs = (
self._data.get("Object interpreter mortal decrefs", 0) +
self._data.get("Object mortal decrefs", 0) +
self._data.get("Object interpreter immortal decrefs", 0) +
self._data.get("Object immortal decrefs", 0)
)

result = {}
for key, value in self._data.items():
Expand Down
Loading