diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index fe35372603fdd87..c38f9b09c0bfa85 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -194,9 +194,20 @@ General Options Turn on internal statistics gathering. + Define the ``Py_STATS`` macro. + + Add functions to the :mod:`sys` module: + + * :func:`sys._stats_on`: Turns on stats gathering (stats gathering is on by + default). + * :func:`sys._stats_off`: Turns off stats gathering (stats gathering is on + by default). + * :func:`sys._stats_clear`: Clears the stats. + * :func:`sys._stats_dump`: Dump stats to file, and clears the stats. + The statistics will be dumped to a arbitrary (probably unique) file in - ``/tmp/py_stats/``, or ``C:\temp\py_stats\`` on Windows. If that directory - does not exist, results will be printed on stdout. + ``/tmp/py_stats/`` (Unix) or ``C:\temp\py_stats\`` (Windows). If that + directory does not exist, results will be printed on stdout. Use ``Tools/scripts/summarize_stats.py`` to read the stats. diff --git a/Include/Python.h b/Include/Python.h index 07f6c202a7f1261..dbc96941646cb24 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -41,6 +41,7 @@ #include "pymem.h" #include "pytypedefs.h" #include "pybuffer.h" +#include "pystats.h" #include "object.h" #include "objimpl.h" #include "typeslots.h" diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h new file mode 100644 index 000000000000000..2e1706a346de45e --- /dev/null +++ b/Include/cpython/pystats.h @@ -0,0 +1,122 @@ +// Statistics on Python performance. +// +// Main API: +// +// - _Py_INCREF_STAT_INC() and _Py_DECREF_STAT_INC() used by Py_INCREF() +// and Py_DECREF(). +// - _Py_stats +// - _Py_StatsClear() +// - _Py_PrintSpecializationStats() +// +// See sys._stats_on() and sys._stats_off() functions. +// +// Python must be built with ./configure --enable-pystats to define the +// Py_STATS macro. +// +// Define _PY_INTERPRETER macro to increment interpreter_increfs and +// interpreter_decrefs. Otherwise, increment increfs and decrefs. + +#ifndef Py_CPYTHON_PYSTATS_H +# error "this header file must not be included directly" +#endif + +#define SPECIALIZATION_FAILURE_KINDS 36 + +/* Stats for determining who is calling PyEval_EvalFrame */ +#define EVAL_CALL_TOTAL 0 +#define EVAL_CALL_VECTOR 1 +#define EVAL_CALL_GENERATOR 2 +#define EVAL_CALL_LEGACY 3 +#define EVAL_CALL_FUNCTION_VECTORCALL 4 +#define EVAL_CALL_BUILD_CLASS 5 +#define EVAL_CALL_SLOT 6 +#define EVAL_CALL_FUNCTION_EX 7 +#define EVAL_CALL_API 8 +#define EVAL_CALL_METHOD 9 + +#define EVAL_CALL_KINDS 10 + +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; + uint64_t eval_calls[EVAL_CALL_KINDS]; +} CallStats; + +typedef struct _object_stats { + uint64_t increfs; + uint64_t decrefs; + uint64_t interpreter_increfs; + uint64_t interpreter_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; + uint64_t dict_dematerialized; + uint64_t type_cache_hits; + uint64_t type_cache_misses; + uint64_t type_cache_dunder_hits; + uint64_t type_cache_dunder_misses; + uint64_t type_cache_collisions; + uint64_t optimization_attempts; + uint64_t optimization_traces_created; + uint64_t optimization_traces_executed; + uint64_t optimization_uops_executed; + /* Temporary value used during GC */ + uint64_t object_visits; +} ObjectStats; + +typedef struct _gc_stats { + uint64_t collections; + uint64_t object_visits; + uint64_t objects_collected; +} GCStats; + +typedef struct _stats { + OpcodeStats opcode_stats[256]; + CallStats call_stats; + ObjectStats object_stats; + GCStats *gc_stats; +} PyStats; + + +extern PyStats _Py_stats_struct; + +// Export for shared extensions like 'math' +PyAPI_DATA(PyStats*) _Py_stats; + +extern void _Py_StatsClear(void); +extern void _Py_PrintSpecializationStats(int to_file); + +#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) +#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) +#endif diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index f5127a81144353b..7c6629074758da7 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -268,17 +268,17 @@ extern int _PyStaticCode_Init(PyCodeObject *co); #ifdef Py_STATS -#define STAT_INC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name++; } while (0) -#define STAT_DEC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name--; } while (0) -#define OPCODE_EXE_INC(opname) do { if (_py_stats) _py_stats->opcode_stats[opname].execution_count++; } while (0) -#define CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.name++; } while (0) -#define OBJECT_STAT_INC(name) do { if (_py_stats) _py_stats->object_stats.name++; } while (0) +#define STAT_INC(opname, name) do { if (_Py_stats) _Py_stats->opcode_stats[opname].specialization.name++; } while (0) +#define STAT_DEC(opname, name) do { if (_Py_stats) _Py_stats->opcode_stats[opname].specialization.name--; } while (0) +#define OPCODE_EXE_INC(opname) do { if (_Py_stats) _Py_stats->opcode_stats[opname].execution_count++; } while (0) +#define CALL_STAT_INC(name) do { if (_Py_stats) _Py_stats->call_stats.name++; } while (0) +#define OBJECT_STAT_INC(name) do { if (_Py_stats) _Py_stats->object_stats.name++; } while (0) #define OBJECT_STAT_INC_COND(name, cond) \ - do { if (_py_stats && cond) _py_stats->object_stats.name++; } while (0) -#define EVAL_CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.eval_calls[name]++; } while (0) + do { if (_Py_stats && cond) _Py_stats->object_stats.name++; } while (0) +#define EVAL_CALL_STAT_INC(name) do { if (_Py_stats) _Py_stats->call_stats.eval_calls[name]++; } while (0) #define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \ - do { if (_py_stats && PyFunction_Check(callable)) _py_stats->call_stats.eval_calls[name]++; } while (0) -#define GC_STAT_ADD(gen, name, n) do { if (_py_stats) _py_stats->gc_stats[(gen)].name += (n); } while (0) + do { if (_Py_stats && PyFunction_Check(callable)) _Py_stats->call_stats.eval_calls[name]++; } while (0) +#define GC_STAT_ADD(gen, name, n) do { if (_Py_stats) _Py_stats->gc_stats[(gen)].name += (n); } while (0) // Export for '_opcode' shared extension PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void); diff --git a/Include/object.h b/Include/object.h index de2a1ce0f3c4ddd..b94b2907e4f1633 100644 --- a/Include/object.h +++ b/Include/object.h @@ -51,8 +51,6 @@ 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 diff --git a/Include/pystats.h b/Include/pystats.h index b1957596745f002..2915a4575341687 100644 --- a/Include/pystats.h +++ b/Include/pystats.h @@ -1,4 +1,9 @@ - +// Statistics on Python performance (public API). +// +// Define _Py_INCREF_STAT_INC() and _Py_DECREF_STAT_INC() used by Py_INCREF() +// and Py_DECREF(). +// +// See also Include/cpython/pystats.h for the full API. #ifndef Py_PYSTATS_H #define Py_PYSTATS_H @@ -6,119 +11,16 @@ extern "C" { #endif -#ifdef Py_STATS - -#define SPECIALIZATION_FAILURE_KINDS 36 - -/* Stats for determining who is calling PyEval_EvalFrame */ -#define EVAL_CALL_TOTAL 0 -#define EVAL_CALL_VECTOR 1 -#define EVAL_CALL_GENERATOR 2 -#define EVAL_CALL_LEGACY 3 -#define EVAL_CALL_FUNCTION_VECTORCALL 4 -#define EVAL_CALL_BUILD_CLASS 5 -#define EVAL_CALL_SLOT 6 -#define EVAL_CALL_FUNCTION_EX 7 -#define EVAL_CALL_API 8 -#define EVAL_CALL_METHOD 9 - -#define EVAL_CALL_KINDS 10 - -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; - uint64_t eval_calls[EVAL_CALL_KINDS]; -} CallStats; - -typedef struct _object_stats { - uint64_t increfs; - uint64_t decrefs; - uint64_t interpreter_increfs; - uint64_t interpreter_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; - uint64_t dict_dematerialized; - uint64_t type_cache_hits; - uint64_t type_cache_misses; - uint64_t type_cache_dunder_hits; - uint64_t type_cache_dunder_misses; - uint64_t type_cache_collisions; - uint64_t optimization_attempts; - uint64_t optimization_traces_created; - uint64_t optimization_traces_executed; - uint64_t optimization_uops_executed; - /* Temporary value used during GC */ - uint64_t object_visits; -} ObjectStats; - -typedef struct _gc_stats { - uint64_t collections; - uint64_t object_visits; - uint64_t objects_collected; -} GCStats; - -typedef struct _stats { - OpcodeStats opcode_stats[256]; - CallStats call_stats; - ObjectStats object_stats; - GCStats *gc_stats; -} PyStats; - - -PyAPI_DATA(PyStats) _py_stats_struct; -PyAPI_DATA(PyStats *) _py_stats; - -extern void _Py_StatsClear(void); -extern void _Py_PrintSpecializationStats(int to_file); - -#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) - +#if defined(Py_STATS) && !defined(Py_LIMITED_API) +# define Py_CPYTHON_PYSTATS_H +# include "cpython/pystats.h" +# undef Py_CPYTHON_PYSTATS_H #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) - -#endif - -#else - -#define _Py_INCREF_STAT_INC() ((void)0) -#define _Py_DECREF_STAT_INC() ((void)0) - +# define _Py_INCREF_STAT_INC() ((void)0) +# define _Py_DECREF_STAT_INC() ((void)0) #endif // !Py_STATS #ifdef __cplusplus } #endif -#endif /* !Py_PYSTATs_H */ +#endif // !Py_PYSTATS_H diff --git a/Makefile.pre.in b/Makefile.pre.in index 0870cb3d899d451..0e7492370724503 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1724,6 +1724,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/cpython/pylifecycle.h \ $(srcdir)/Include/cpython/pymem.h \ $(srcdir)/Include/cpython/pystate.h \ + $(srcdir)/Include/cpython/pystats.h \ $(srcdir)/Include/cpython/pythonrun.h \ $(srcdir)/Include/cpython/pythread.h \ $(srcdir)/Include/cpython/setobject.h \ diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 35a35091bf4511c..632cabdf4bcfbd6 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1200,8 +1200,8 @@ gc_collect_main(PyThreadState *tstate, int generation, { GC_STAT_ADD(generation, collections, 1); #ifdef Py_STATS - if (_py_stats) { - _py_stats->object_stats.object_visits = 0; + if (_Py_stats) { + _Py_stats->object_stats.object_visits = 0; } #endif int i; @@ -1362,10 +1362,10 @@ gc_collect_main(PyThreadState *tstate, int generation, GC_STAT_ADD(generation, objects_collected, m); #ifdef Py_STATS - if (_py_stats) { + if (_Py_stats) { GC_STAT_ADD(generation, object_visits, - _py_stats->object_stats.object_visits); - _py_stats->object_stats.object_visits = 0; + _Py_stats->object_stats.object_visits); + _Py_stats->object_stats.object_visits = 0; } #endif diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 4cd095b28e510f4..5b42b36c4b380cf 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -176,6 +176,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index af1669209a9049f..3d98c1f69549d72 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -492,6 +492,9 @@ Include\cpython + + Include\cpython + Include\cpython diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 4b7c4448e0ea259..81fbb7982ad11cc 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -64,7 +64,7 @@ do { \ frame->prev_instr = next_instr++; \ OPCODE_EXE_INC(op); \ - if (_py_stats) _py_stats->opcode_stats[lastopcode].pair_count[op]++; \ + if (_Py_stats) _Py_stats->opcode_stats[lastopcode].pair_count[op]++; \ lastopcode = op; \ } while (0) #else diff --git a/Python/initconfig.c b/Python/initconfig.c index 3281b3c6aea45fc..ee6215c8e9174e3 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -2103,7 +2103,7 @@ config_read(PyConfig *config, int compute_path_config) #ifdef Py_STATS if (config_get_xoption(config, L"pystats")) { - _py_stats = &_py_stats_struct; + _Py_stats = &_Py_stats_struct; } #endif diff --git a/Python/specialize.c b/Python/specialize.c index a794f146c188ecd..b0db8b37619e57c 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -22,8 +22,8 @@ #ifdef Py_STATS GCStats _py_gc_stats[NUM_GENERATIONS] = { 0 }; -PyStats _py_stats_struct = { .gc_stats = &_py_gc_stats[0] }; -PyStats *_py_stats = NULL; +PyStats _Py_stats_struct = { .gc_stats = &_py_gc_stats[0] }; +PyStats *_Py_stats = NULL; #define ADD_STAT_TO_DICT(res, field) \ do { \ @@ -83,7 +83,7 @@ add_stat_dict( int opcode, const char *name) { - SpecializationStats *stats = &_py_stats_struct.opcode_stats[opcode].specialization; + SpecializationStats *stats = &_Py_stats_struct.opcode_stats[opcode].specialization; PyObject *d = stats_to_dict(stats); if (d == NULL) { return -1; @@ -231,8 +231,8 @@ _Py_StatsClear(void) for (int i = 0; i < NUM_GENERATIONS; i++) { _py_gc_stats[i] = (GCStats) { 0 }; } - _py_stats_struct = (PyStats) { 0 }; - _py_stats_struct.gc_stats = _py_gc_stats; + _Py_stats_struct = (PyStats) { 0 }; + _Py_stats_struct.gc_stats = _py_gc_stats; } void @@ -268,7 +268,7 @@ _Py_PrintSpecializationStats(int to_file) else { fprintf(out, "Specialization stats:\n"); } - print_stats(out, &_py_stats_struct); + print_stats(out, &_Py_stats_struct); if (out != stderr) { fclose(out); } @@ -278,8 +278,8 @@ _Py_PrintSpecializationStats(int to_file) #define SPECIALIZATION_FAIL(opcode, kind) \ do { \ - if (_py_stats) { \ - _py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++; \ + if (_Py_stats) { \ + _Py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++; \ } \ } while (0) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 0ec763c7aa7cf85..a69d16037c3d7e5 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2115,7 +2115,7 @@ static PyObject * sys__stats_on_impl(PyObject *module) /*[clinic end generated code: output=aca53eafcbb4d9fe input=8ddc6df94e484f3a]*/ { - _py_stats = &_py_stats_struct; + _Py_stats = &_Py_stats_struct; Py_RETURN_NONE; } @@ -2129,7 +2129,7 @@ static PyObject * sys__stats_off_impl(PyObject *module) /*[clinic end generated code: output=1534c1ee63812214 input=b3e50e71ecf29f66]*/ { - _py_stats = NULL; + _Py_stats = NULL; Py_RETURN_NONE; }