From 635f67781d8961b2d77d9370108d7c515f4a9d28 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 19 Apr 2022 12:17:37 +0100 Subject: [PATCH 1/6] Add incref/decref stats --- Include/internal/pycore_code.h | 2 ++ Include/object.h | 2 ++ Python/specialize.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 8c868bcd5b5cbe..62e08f8424fad9 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -311,6 +311,8 @@ typedef struct _object_stats { uint64_t dict_materialized_new_key; uint64_t dict_materialized_too_big; uint64_t dict_materialized_str_subclass; + uint64_t increfs; + uint64_t decrefs; } ObjectStats; typedef struct _stats { diff --git a/Include/object.h b/Include/object.h index 0b4b55ea1ded9e..cf4c6084373c4b 100644 --- a/Include/object.h +++ b/Include/object.h @@ -486,6 +486,7 @@ static inline void Py_INCREF(PyObject *op) #ifdef Py_REF_DEBUG _Py_RefTotal++; #endif + OBJECT_STAT_INC(increfs); op->ob_refcnt++; #endif } @@ -506,6 +507,7 @@ static inline void Py_DECREF( #ifdef Py_REF_DEBUG _Py_RefTotal--; #endif + OBJECT_STAT_INC(decrefs); if (--op->ob_refcnt != 0) { #ifdef Py_REF_DEBUG if (op->ob_refcnt < 0) { diff --git a/Python/specialize.c b/Python/specialize.c index 3a8b768549c63d..f12aab66f91cdf 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -186,6 +186,8 @@ print_object_stats(FILE *out, ObjectStats *stats) fprintf(out, "Object allocations: %" PRIu64 "\n", stats->allocations); fprintf(out, "Object frees: %" PRIu64 "\n", stats->frees); fprintf(out, "Object new values: %" PRIu64 "\n", stats->new_values); + fprintf(out, "Objec increfs: %" PRIu64 "\n", stats->increfs); + fprintf(out, "Objec decrefs: %" PRIu64 "\n", stats->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); From 7513c26db22e51f495f1fb951b68f3f9c6db52f1 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 2 Feb 2022 16:19:44 +0000 Subject: [PATCH 2/6] Turn on stats --- Include/Python.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Include/Python.h b/Include/Python.h index 52a7aac6ba6cb6..b72d8a8ec2a224 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -5,6 +5,8 @@ #ifndef Py_PYTHON_H #define Py_PYTHON_H +#define Py_STATS 1 + // Since this is a "meta-include" file, no #ifdef __cplusplus / extern "C" { // Include Python header files From 63a1121610289e685b2db6709984ed857b9d9405 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 19 Apr 2022 12:55:41 +0100 Subject: [PATCH 3/6] Fixup refcount stats --- Include/Python.h | 2 -- Include/internal/pycore_code.h | 2 -- Include/object.h | 13 +++++++++++-- Objects/object.c | 5 +++++ Python/specialize.c | 4 ++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Include/Python.h b/Include/Python.h index b72d8a8ec2a224..52a7aac6ba6cb6 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -5,8 +5,6 @@ #ifndef Py_PYTHON_H #define Py_PYTHON_H -#define Py_STATS 1 - // Since this is a "meta-include" file, no #ifdef __cplusplus / extern "C" { // Include Python header files diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 62e08f8424fad9..8c868bcd5b5cbe 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -311,8 +311,6 @@ typedef struct _object_stats { uint64_t dict_materialized_new_key; uint64_t dict_materialized_too_big; uint64_t dict_materialized_str_subclass; - uint64_t increfs; - uint64_t decrefs; } ObjectStats; typedef struct _stats { diff --git a/Include/object.h b/Include/object.h index cf4c6084373c4b..4a58742ec84dec 100644 --- a/Include/object.h +++ b/Include/object.h @@ -475,8 +475,16 @@ PyAPI_FUNC(void) Py_DecRef(PyObject *); PyAPI_FUNC(void) _Py_IncRef(PyObject *); PyAPI_FUNC(void) _Py_DecRef(PyObject *); +#ifdef Py_STATS +PyAPI_DATA(uint64_t) _Py_IncrefTotal; +PyAPI_DATA(uint64_t) _Py_DecrefTotal; +#endif + static inline void Py_INCREF(PyObject *op) { +#ifdef Py_STATS + _Py_IncrefTotal++; +#endif #if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000 // Stable ABI for Python 3.10 built in debug mode. _Py_IncRef(op); @@ -486,7 +494,6 @@ static inline void Py_INCREF(PyObject *op) #ifdef Py_REF_DEBUG _Py_RefTotal++; #endif - OBJECT_STAT_INC(increfs); op->ob_refcnt++; #endif } @@ -498,6 +505,9 @@ static inline void Py_DECREF( #endif PyObject *op) { +#ifdef Py_STATS + _Py_DecrefTotal++; +#endif #if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000 // Stable ABI for Python 3.10 built in debug mode. _Py_DecRef(op); @@ -507,7 +517,6 @@ static inline void Py_DECREF( #ifdef Py_REF_DEBUG _Py_RefTotal--; #endif - OBJECT_STAT_INC(decrefs); if (--op->ob_refcnt != 0) { #ifdef Py_REF_DEBUG if (op->ob_refcnt < 0) { diff --git a/Objects/object.c b/Objects/object.c index fe2d76f578e2ad..e9bfeed754d3dd 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -72,6 +72,11 @@ _PyDebug_PrintTotalRefs(void) { } #endif /* Py_REF_DEBUG */ +#ifdef Py_STATS +uint64_t _Py_IncrefTotal = 0; +uint64_t _Py_DecrefTotal = 0; +#endif + /* Object allocation routines used by NEWOBJ and NEWVAROBJ macros. These are used by the individual routines for object creation. Do not call them otherwise, they do not initialize the object! */ diff --git a/Python/specialize.c b/Python/specialize.c index f12aab66f91cdf..028b538b024b09 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -186,8 +186,8 @@ print_object_stats(FILE *out, ObjectStats *stats) fprintf(out, "Object allocations: %" PRIu64 "\n", stats->allocations); fprintf(out, "Object frees: %" PRIu64 "\n", stats->frees); fprintf(out, "Object new values: %" PRIu64 "\n", stats->new_values); - fprintf(out, "Objec increfs: %" PRIu64 "\n", stats->increfs); - fprintf(out, "Objec decrefs: %" PRIu64 "\n", stats->decrefs); + fprintf(out, "Object increfs: %" PRIu64 "\n", _Py_IncrefTotal); + fprintf(out, "Object decrefs: %" PRIu64 "\n", _Py_DecrefTotal); 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); From e99f77bca82422f97b233524adb1afedec6fa68c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 16 May 2022 11:30:27 +0100 Subject: [PATCH 4/6] Move stats to their own header file and make sure we have all decrefs. --- Include/internal/pycore_code.h | 49 ------------------- Include/object.h | 17 ++----- Include/pystats.h | 77 ++++++++++++++++++++++++++++++ Makefile.pre.in | 1 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 ++ Python/ceval.c | 3 ++ Python/specialize.c | 4 +- 8 files changed, 92 insertions(+), 63 deletions(-) create mode 100644 Include/pystats.h diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index e11d1f05129c67..96cc9dcae11a71 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -265,53 +265,6 @@ extern int _PyStaticCode_InternStrings(PyCodeObject *co); #ifdef Py_STATS -#define SPECIALIZATION_FAILURE_KINDS 30 - -typedef struct _specialization_stats { - uint64_t success; - uint64_t failure; - uint64_t hit; - uint64_t deferred; - uint64_t miss; - uint64_t deopt; - uint64_t failure_kinds[SPECIALIZATION_FAILURE_KINDS]; -} SpecializationStats; - -typedef struct _opcode_stats { - SpecializationStats specialization; - uint64_t execution_count; - uint64_t pair_count[256]; -} OpcodeStats; - -typedef struct _call_stats { - uint64_t inlined_py_calls; - uint64_t pyeval_calls; - uint64_t frames_pushed; - uint64_t frame_objects_created; -} CallStats; - -typedef struct _object_stats { - uint64_t allocations; - uint64_t allocations512; - uint64_t allocations4k; - uint64_t allocations_big; - uint64_t frees; - uint64_t to_freelist; - uint64_t from_freelist; - uint64_t new_values; - uint64_t dict_materialized_on_request; - uint64_t dict_materialized_new_key; - uint64_t dict_materialized_too_big; - uint64_t dict_materialized_str_subclass; -} ObjectStats; - -typedef struct _stats { - OpcodeStats opcode_stats[256]; - CallStats call_stats; - ObjectStats object_stats; -} PyStats; - -extern PyStats _py_stats; #define STAT_INC(opname, name) _py_stats.opcode_stats[opname].specialization.name++ #define STAT_DEC(opname, name) _py_stats.opcode_stats[opname].specialization.name-- @@ -321,8 +274,6 @@ extern PyStats _py_stats; #define OBJECT_STAT_INC_COND(name, cond) \ do { if (cond) _py_stats.object_stats.name++; } while (0) -extern void _Py_PrintSpecializationStats(int to_file); - // Used by the _opcode extension which is built as a shared library PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void); diff --git a/Include/object.h b/Include/object.h index f29260894e08c3..f01b9fa86d0148 100644 --- a/Include/object.h +++ b/Include/object.h @@ -51,6 +51,8 @@ A standard interface exists for objects that contain an array of items whose size is determined when the object is allocated. */ +#include "pystats.h" + /* Py_DEBUG implies Py_REF_DEBUG. */ #if defined(Py_DEBUG) && !defined(Py_REF_DEBUG) # define Py_REF_DEBUG @@ -488,16 +490,9 @@ PyAPI_FUNC(void) Py_DecRef(PyObject *); PyAPI_FUNC(void) _Py_IncRef(PyObject *); PyAPI_FUNC(void) _Py_DecRef(PyObject *); -#ifdef Py_STATS -PyAPI_DATA(uint64_t) _Py_IncrefTotal; -PyAPI_DATA(uint64_t) _Py_DecrefTotal; -#endif - static inline void Py_INCREF(PyObject *op) { -#ifdef Py_STATS - _Py_IncrefTotal++; -#endif + _Py_INCREF_STAT_INC(); #if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000 // Stable ABI for Python 3.10 built in debug mode. _Py_IncRef(op); @@ -514,10 +509,6 @@ static inline void Py_INCREF(PyObject *op) # define Py_INCREF(op) Py_INCREF(_PyObject_CAST(op)) #endif - -#ifdef Py_STATS - _Py_DecrefTotal++; -#endif #if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000 // Stable ABI for limited C API version 3.10 of Python debug build static inline void Py_DECREF(PyObject *op) { @@ -528,6 +519,7 @@ static inline void Py_DECREF(PyObject *op) { #elif defined(Py_REF_DEBUG) static inline void Py_DECREF(const char *filename, int lineno, PyObject *op) { + _Py_DECREF_STAT_INC(); _Py_RefTotal--; if (--op->ob_refcnt != 0) { if (op->ob_refcnt < 0) { @@ -543,6 +535,7 @@ static inline void Py_DECREF(const char *filename, int lineno, PyObject *op) #else static inline void Py_DECREF(PyObject *op) { + _Py_DECREF_STAT_INC(); // Non-limited C API and limited C API for Python 3.9 and older access // directly PyObject.ob_refcnt. if (--op->ob_refcnt == 0) { diff --git a/Include/pystats.h b/Include/pystats.h new file mode 100644 index 00000000000000..bc05dd864c63a7 --- /dev/null +++ b/Include/pystats.h @@ -0,0 +1,77 @@ + + +#ifndef Py_PYSTATS_H +#define Py_PYSTATS_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef Py_STATS + +#define SPECIALIZATION_FAILURE_KINDS 32 + +typedef struct _specialization_stats { + uint64_t success; + uint64_t failure; + uint64_t hit; + uint64_t deferred; + uint64_t miss; + uint64_t deopt; + uint64_t failure_kinds[SPECIALIZATION_FAILURE_KINDS]; +} SpecializationStats; + +typedef struct _opcode_stats { + SpecializationStats specialization; + uint64_t execution_count; + uint64_t pair_count[256]; +} OpcodeStats; + +typedef struct _call_stats { + uint64_t inlined_py_calls; + uint64_t pyeval_calls; + uint64_t frames_pushed; + uint64_t frame_objects_created; +} CallStats; + +typedef struct _object_stats { + uint64_t increfs; + uint64_t decrefs; + uint64_t allocations; + uint64_t allocations512; + uint64_t allocations4k; + uint64_t allocations_big; + uint64_t frees; + uint64_t to_freelist; + uint64_t from_freelist; + uint64_t new_values; + uint64_t dict_materialized_on_request; + uint64_t dict_materialized_new_key; + uint64_t dict_materialized_too_big; + uint64_t dict_materialized_str_subclass; +} ObjectStats; + +typedef struct _stats { + OpcodeStats opcode_stats[256]; + CallStats call_stats; + ObjectStats object_stats; +} PyStats; + +PyAPI_DATA(PyStats) _py_stats; + +extern void _Py_PrintSpecializationStats(int to_file); + + +#define _Py_INCREF_STAT_INC() _py_stats.object_stats.increfs++ +#define _Py_DECREF_STAT_INC() _py_stats.object_stats.decrefs++ + +#else + +#define _Py_INCREF_STAT_INC() ((void)0) +#define _Py_DECREF_STAT_INC() ((void)0) + +#endif // !Py_STATS + +#ifdef __cplusplus +} +#endif +#endif /* !Py_PYSTATs_H */ diff --git a/Makefile.pre.in b/Makefile.pre.in index 515c18cc216664..a178dd0a65c734 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1509,6 +1509,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/pymem.h \ $(srcdir)/Include/pyport.h \ $(srcdir)/Include/pystate.h \ + $(srcdir)/Include/pystats.h \ $(srcdir)/Include/pystrcmp.h \ $(srcdir)/Include/pystrtod.h \ $(srcdir)/Include/pythonrun.h \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index a35884b3c35887..be76f1bc55859a 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -280,6 +280,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index ff42cc92c4bd23..5573e0020491a6 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -174,6 +174,9 @@ Include + + Include + Include diff --git a/Python/ceval.c b/Python/ceval.c index c73218fcf307ef..70e8d478b95d7d 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -54,7 +54,9 @@ #undef Py_DECREF #define Py_DECREF(arg) \ + _Py_DECREF_STAT_INC(); \ do { \ + _Py_DECREF_STAT_INC(); \ PyObject *op = _PyObject_CAST(arg); \ if (--op->ob_refcnt == 0) { \ destructor dealloc = Py_TYPE(op)->tp_dealloc; \ @@ -78,6 +80,7 @@ #undef _Py_DECREF_SPECIALIZED #define _Py_DECREF_SPECIALIZED(arg, dealloc) \ do { \ + _Py_DECREF_STAT_INC(); \ PyObject *op = _PyObject_CAST(arg); \ if (--op->ob_refcnt == 0) { \ destructor d = (destructor)(dealloc); \ diff --git a/Python/specialize.c b/Python/specialize.c index a72bc69cea1c51..6a91389f856106 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -191,8 +191,8 @@ 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 new values: %" PRIu64 "\n", stats->new_values); - fprintf(out, "Object increfs: %" PRIu64 "\n", _Py_IncrefTotal); - fprintf(out, "Object decrefs: %" PRIu64 "\n", _Py_DecrefTotal); + fprintf(out, "Object increfs: %" PRIu64 "\n", stats->increfs); + fprintf(out, "Object decrefs: %" PRIu64 "\n", stats->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); From 37dc9453229fffd00c8fe2aa6da7cb0a9ef7fc46 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 16 May 2022 11:59:35 +0100 Subject: [PATCH 5/6] Add ratios to allocation numbers --- Include/internal/pycore_object.h | 2 ++ Python/ceval.c | 1 - Tools/scripts/summarize_stats.py | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index f022f8246989d1..cc50418e2ef40f 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -34,6 +34,7 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc( static inline void _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) { + _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG _Py_RefTotal--; #endif @@ -51,6 +52,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) static inline void _Py_DECREF_NO_DEALLOC(PyObject *op) { + _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG _Py_RefTotal--; #endif diff --git a/Python/ceval.c b/Python/ceval.c index 70e8d478b95d7d..c81d0efff9b9b3 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -54,7 +54,6 @@ #undef Py_DECREF #define Py_DECREF(arg) \ - _Py_DECREF_STAT_INC(); \ do { \ _Py_DECREF_STAT_INC(); \ PyObject *op = _PyObject_CAST(arg); \ diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index bc528ca316f931..91b190114008ea 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -8,6 +8,7 @@ from datetime import date import itertools import argparse +import sys if os.name == "nt": DEFAULT_DIR = "c:\\temp\\py_stats\\" @@ -88,7 +89,11 @@ def gather_stats(): for filename in os.listdir(DEFAULT_DIR): with open(os.path.join(DEFAULT_DIR, filename)) as fd: for line in fd: - key, value = line.split(":") + try: + key, value = line.split(":") + except ValueError: + print (f"Unparsable line: '{line.strip()}' in {filename}", file=sys.stderr) + continue key = key.strip() value = int(value) stats[key] += value @@ -265,17 +270,20 @@ def emit_call_stats(stats): def emit_object_stats(stats): with Section("Object stats", summary="allocations, frees and dict materializatons"): - total = stats.get("Object new values") + total_materializations = stats.get("Object new values") + total_allocations = stats.get("Object allocations") rows = [] for key, value in stats.items(): if key.startswith("Object"): if "materialize" in key: - materialize = f"{100*value/total:0.1f}%" + ratio = f"{100*value/total_materializations:0.1f}%" + elif "allocations" in key: + ratio = f"{100*value/total_allocations:0.1f}%" else: - materialize = "" + ratio = "" label = key[6:].strip() label = label[0].upper() + label[1:] - rows.append((label, value, materialize)) + rows.append((label, value, ratio)) emit_table(("", "Count:", "Ratio:"), rows) def get_total(opcode_stats): From 5e8c9b62ff150797f3b3097e5ddd51440ba9724f Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 16 May 2022 12:03:39 +0100 Subject: [PATCH 6/6] Remove dead data --- Objects/object.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 4df519992e9054..c9bb60eaed87c1 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -72,11 +72,6 @@ _PyDebug_PrintTotalRefs(void) { } #endif /* Py_REF_DEBUG */ -#ifdef Py_STATS -uint64_t _Py_IncrefTotal = 0; -uint64_t _Py_DecrefTotal = 0; -#endif - /* Object allocation routines used by NEWOBJ and NEWVAROBJ macros. These are used by the individual routines for object creation. Do not call them otherwise, they do not initialize the object! */