diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index fe35372603fdd87..8cf0ff5776b2afe 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -192,14 +192,66 @@ General Options .. cmdoption:: --enable-pystats - Turn on internal statistics gathering. + Turn on internal Python performance statistics gathering. + + By default, statistics gathering is off. Use ``python3 -X pystats`` command + or set ``PYTHONSTATS=1`` environment variable to turn on statistics + gathering at Python startup and to dump statistics at Python exit. + + Effects: + + * Add :option:`-X pystats <-X>` command line option. + * Add :envvar:`PYTHONSTATS` environment variable. + * Define the ``Py_STATS`` macro. + * Add functions to the :mod:`sys` module: + + * :func:`sys._stats_on`: Turns on statistics gathering. + * :func:`sys._stats_off`: Turns off statistics gathering. + * :func:`sys._stats_clear`: Clears the statistics. + * :func:`sys._stats_dump`: Dump statistics to file, and clears the statistics. 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. + Statistics: + + * Opcode: + + * Specialization: success, failure, hit, deferred, miss, deopt, failures; + * Execution count; + * Pair count. + + * Call: + + * Inlined Python calls; + * PyEval calls; + * Frames pushed; + * Frame object created; + * Eval calls: vector, generator, legacy, function VECTORCALL, build class, + slot, function "ex", API, method. + + * Object: + + * incref and decref; + * interpreter incref and decref; + * allocations: all, 512 bytes, 4 kiB, big; + * free; + * to/from free lists; + * dictionary materialized/dematerialized; + * type cache; + * optimization attemps; + * optimization traces created/executed; + * uops executed. + + * Garbage collector: + + * Garbage collections; + * Objects visited; + * Objects collected. + .. versionadded:: 3.11 .. cmdoption:: --disable-gil diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 7fb7a9868be9261..ee130467824daaa 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -215,6 +215,11 @@ typedef struct PyConfig { // If non-zero, we believe we're running from a source tree. int _is_python_build; + +#ifdef Py_STATS + // If non-zero, turns on statistics gathering. + int _pystats; +#endif } PyConfig; PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config); diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h new file mode 100644 index 000000000000000..150e16faa96ca18 --- /dev/null +++ b/Include/cpython/pystats.h @@ -0,0 +1,120 @@ +// Statistics on Python performance. +// +// API: +// +// - _Py_INCREF_STAT_INC() and _Py_DECREF_STAT_INC() used by Py_INCREF() +// and Py_DECREF(). +// - _Py_stats variable +// +// Functions of the sys module: +// +// - sys._stats_on() +// - sys._stats_off() +// - sys._stats_clear() +// - sys._stats_dump() +// +// 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; + + +// Export for shared extensions like 'math' +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) +#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_ceval.h b/Include/internal/pycore_ceval.h index e9535023cec46bb..73024b33bc0b22b 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -69,7 +69,7 @@ extern int _PyEval_SetAsyncGenFinalizer(PyObject *); extern int _PyEval_GetCoroutineOriginTrackingDepth(void); extern int _PyEval_SetCoroutineOriginTrackingDepth(int depth); -extern void _PyEval_Fini(void); +extern void _PyEval_Fini(PyThreadState *tstate); extern PyObject* _PyEval_GetBuiltins(PyThreadState *tstate); 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/internal/pycore_pystats.h b/Include/internal/pycore_pystats.h new file mode 100644 index 000000000000000..f8635b69a3f73ec --- /dev/null +++ b/Include/internal/pycore_pystats.h @@ -0,0 +1,21 @@ +#ifndef Py_INTERNAL_PYSTATS_H +#define Py_INTERNAL_PYSTATS_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#ifdef Py_STATS +extern void _Py_StatsOn(void); +extern void _Py_StatsOff(void); +extern void _Py_StatsClear(void); +extern void _Py_PrintSpecializationStats(int to_file); +#endif + +#ifdef __cplusplus +} +#endif +#endif // !Py_INTERNAL_PYSTATS_H diff --git a/Include/pystats.h b/Include/pystats.h index b1957596745f002..acfa32201711e07 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 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/Lib/test/test_embed.py b/Lib/test/test_embed.py index 50c9f61017e022c..7f1a4e665f3b5d0 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -26,6 +26,7 @@ PYMEM_ALLOCATOR_NOT_SET = 0 PYMEM_ALLOCATOR_DEBUG = 2 PYMEM_ALLOCATOR_MALLOC = 3 +Py_STATS = hasattr(sys, '_stats_on') # _PyCoreConfig_InitCompatConfig() API_COMPAT = 1 @@ -512,6 +513,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'safe_path': 0, '_is_python_build': IGNORE_CONFIG, } + if Py_STATS: + CONFIG_COMPAT['_pystats'] = 0 if MS_WINDOWS: CONFIG_COMPAT.update({ 'legacy_windows_stdio': 0, @@ -895,6 +898,8 @@ def test_init_from_config(self): 'check_hash_pycs_mode': 'always', 'pathconfig_warnings': 0, } + if Py_STATS: + config['_pystats'] = 1 self.check_all_configs("test_init_from_config", config, preconfig, api=API_COMPAT) @@ -927,6 +932,8 @@ def test_init_compat_env(self): 'safe_path': 1, 'int_max_str_digits': 4567, } + if Py_STATS: + config['_pystats'] = 1 self.check_all_configs("test_init_compat_env", config, preconfig, api=API_COMPAT) @@ -960,6 +967,8 @@ def test_init_python_env(self): 'safe_path': 1, 'int_max_str_digits': 4567, } + if Py_STATS: + config['_pystats'] = 1 self.check_all_configs("test_init_python_env", config, preconfig, api=API_PYTHON) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index e8a99244a3a28de..268ba9598c93764 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1195,6 +1195,15 @@ class MyType: get_objects = sys.getobjects(3, MyType) self.assertEqual(len(get_objects), 3) + @unittest.skipUnless(hasattr(sys, '_stats_on'), 'need Py_STATS build') + def test_pystats(self): + # Call the functions, just check that they don't crash + # Cannot save/restore state. + sys._stats_on() + sys._stats_off() + sys._stats_clear() + sys._stats_dump() + @test.support.cpython_only class UnraisableHookTest(unittest.TestCase): diff --git a/Makefile.pre.in b/Makefile.pre.in index 7b67738f4341a2b..85a800549017e2a 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 \ @@ -1798,6 +1799,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_pymem.h \ $(srcdir)/Include/internal/pycore_pymem_init.h \ $(srcdir)/Include/internal/pycore_pystate.h \ + $(srcdir)/Include/internal/pycore_pystats.h \ $(srcdir)/Include/internal/pycore_pythonrun.h \ $(srcdir)/Include/internal/pycore_pythread.h \ $(srcdir)/Include/internal/pycore_range.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..04752a8029acc22 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -176,6 +176,7 @@ + @@ -261,6 +262,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index af1669209a9049f..4ad027784669258 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -492,6 +492,9 @@ Include\cpython + + Include\cpython + Include\cpython @@ -693,6 +696,9 @@ Include\internal + + Include\internal + Include\internal diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 7ee64b22925f0c6..bc991020d0fa77e 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -708,6 +708,10 @@ static int test_init_from_config(void) config.pathconfig_warnings = 0; config.safe_path = 1; +#ifdef Py_STATS + putenv("PYTHONSTATS="); + config._pystats = 1; +#endif putenv("PYTHONINTMAXSTRDIGITS=6666"); config.int_max_str_digits = 31337; @@ -778,6 +782,9 @@ static void set_most_env_vars(void) putenv("PYTHONPLATLIBDIR=env_platlibdir"); putenv("PYTHONSAFEPATH=1"); putenv("PYTHONINTMAXSTRDIGITS=4567"); +#ifdef Py_STATS + putenv("PYTHONSTATS=1"); +#endif } diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 7c9ad07cc7207b6..e32d0037fe53044 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -2,11 +2,12 @@ #include "Python.h" #include "pycore_atomic.h" // _Py_atomic_int #include "pycore_ceval.h" // _PyEval_SignalReceived() -#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() -#include "pycore_pylifecycle.h" // _PyErr_Print() #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_interp.h" // _Py_RunGC() +#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() +#include "pycore_pylifecycle.h" // _PyErr_Print() #include "pycore_pymem.h" // _PyMem_IsPtrFreed() +#include "pycore_pystats.h" // _Py_PrintSpecializationStats() /* Notes about the implementation: @@ -617,10 +618,12 @@ PyEval_InitThreads(void) } void -_PyEval_Fini(void) +_PyEval_Fini(PyThreadState *tstate) { #ifdef Py_STATS - _Py_PrintSpecializationStats(1); + if (tstate->interp->config._pystats) { + _Py_PrintSpecializationStats(1); + } #endif } 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 f9c5c64f9c87591..a0467f51d4834e6 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -9,6 +9,7 @@ #include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig() #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_pystats.h" // _Py_StatsOn() #include "osdefs.h" // DELIM @@ -186,7 +187,11 @@ static const char usage_envvars[] = "PYTHONSAFEPATH : don't prepend a potentially unsafe path to sys.path (-P)\n" "PYTHONUNBUFFERED : disable stdout/stderr buffering (-u)\n" "PYTHONVERBOSE : trace import statements (-v)\n" -"PYTHONWARNINGS=arg : warning control (-W arg)\n"; +"PYTHONWARNINGS=arg : warning control (-W arg)\n" +#ifdef Py_STATS +"PYTHONSTATS : turns on statistics gathering\n" +#endif +; #if defined(MS_WINDOWS) # define PYTHONHOMEHELP "\\python{major}{minor}" @@ -630,6 +635,9 @@ config_check_consistency(const PyConfig *config) assert(config->int_max_str_digits >= 0); // config->use_frozen_modules is initialized later // by _PyConfig_InitImportConfig(). +#ifdef Py_STATS + assert(config->_pystats >= 0); +#endif return 1; } #endif @@ -951,6 +959,9 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) COPY_WSTRLIST(orig_argv); COPY_ATTR(_is_python_build); COPY_ATTR(int_max_str_digits); +#ifdef Py_STATS + COPY_ATTR(_pystats); +#endif #undef COPY_ATTR #undef COPY_WSTR_ATTR @@ -1058,6 +1069,9 @@ _PyConfig_AsDict(const PyConfig *config) SET_ITEM_INT(safe_path); SET_ITEM_INT(_is_python_build); SET_ITEM_INT(int_max_str_digits); +#ifdef Py_STATS + SET_ITEM_INT(_pystats); +#endif return dict; @@ -1365,6 +1379,9 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) GET_UINT(safe_path); GET_UINT(_is_python_build); GET_INT(int_max_str_digits); +#ifdef Py_STATS + GET_UINT(_pystats); +#endif #undef CHECK_VALUE #undef GET_UINT @@ -2116,7 +2133,13 @@ config_read(PyConfig *config, int compute_path_config) #ifdef Py_STATS if (config_get_xoption(config, L"pystats")) { - _py_stats = &_py_stats_struct; + config->_pystats = 1; + } + else if (config_get_env(config, "PYTHONSTATS")) { + config->_pystats = 1; + } + if (config->_pystats < 0) { + config->_pystats = 0; } #endif @@ -2254,6 +2277,13 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime) { return _PyStatus_NO_MEMORY(); } + +#ifdef Py_STATS + if (config->_pystats) { + _Py_StatsOn(); + } +#endif + return _PyStatus_OK(); } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 64c74f433f222c8..b79b6135f5c6246 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1878,7 +1878,7 @@ Py_FinalizeEx(void) finalize_modules(tstate); /* Print debug stats if any */ - _PyEval_Fini(); + _PyEval_Fini(tstate); /* Flush sys.stdout and sys.stderr (again, in case more was printed) */ if (flush_std_files() < 0) { diff --git a/Python/specialize.c b/Python/specialize.c index a794f146c188ecd..a3886f8459f407f 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -13,17 +13,17 @@ #include "pycore_opcode_metadata.h" // _PyOpcode_Caches #include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() - #include // rand() + /* For guidance on adding or extending families of instructions see * ./adaptive.md */ #ifdef Py_STATS GCStats _py_gc_stats[NUM_GENERATIONS] = { 0 }; -PyStats _py_stats_struct = { .gc_stats = &_py_gc_stats[0] }; -PyStats *_py_stats = NULL; +static 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; @@ -93,7 +93,6 @@ add_stat_dict( return err; } -#ifdef Py_STATS PyObject* _Py_GetSpecializationStats(void) { PyObject *stats = PyDict_New(); @@ -120,7 +119,6 @@ _Py_GetSpecializationStats(void) { } return stats; } -#endif #define PRINT_STAT(i, field) \ @@ -218,21 +216,34 @@ print_gc_stats(FILE *out, GCStats *stats) } static void -print_stats(FILE *out, PyStats *stats) { +print_stats(FILE *out, PyStats *stats) +{ print_spec_stats(out, stats->opcode_stats); print_call_stats(out, &stats->call_stats); print_object_stats(out, &stats->object_stats); print_gc_stats(out, stats->gc_stats); } +void +_Py_StatsOn(void) +{ + _Py_stats = &_Py_stats_struct; +} + +void +_Py_StatsOff(void) +{ + _Py_stats = NULL; +} + void _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,26 +279,24 @@ _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); } } -#ifdef Py_STATS - #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) -#endif -#endif +#endif // Py_STATS + #ifndef SPECIALIZATION_FAIL -#define SPECIALIZATION_FAIL(opcode, kind) ((void)0) +# define SPECIALIZATION_FAIL(opcode, kind) ((void)0) #endif // Initialize warmup counters and insert superinstructions. This cannot fail. @@ -1067,7 +1076,7 @@ load_attr_fail_kind(DescriptorClassification kind) } Py_UNREACHABLE(); } -#endif +#endif // Py_STATS static int specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr, @@ -1306,7 +1315,7 @@ binary_subscr_fail_kind(PyTypeObject *container_type, PyObject *sub) } return SPEC_FAIL_OTHER; } -#endif +#endif // Py_STATS static int function_kind(PyCodeObject *code) { @@ -1545,7 +1554,7 @@ _Py_Specialize_StoreSubscr(PyObject *container, PyObject *sub, _Py_CODEUNIT *ins } goto fail; } -#endif +#endif // Py_STATS SPECIALIZATION_FAIL(STORE_SUBSCR, SPEC_FAIL_OTHER); fail: STAT_INC(STORE_SUBSCR, failure); @@ -1690,7 +1699,7 @@ meth_descr_call_fail_kind(int ml_flags) return SPEC_FAIL_CALL_BAD_CALL_FLAGS; } } -#endif +#endif // Py_STATS static int specialize_method_descriptor(PyMethodDescrObject *descr, _Py_CODEUNIT *instr, @@ -1871,7 +1880,7 @@ call_fail_kind(PyObject *callable) } return SPEC_FAIL_OTHER; } -#endif +#endif // Py_STATS /* TODO: @@ -1995,7 +2004,7 @@ binary_op_fail_kind(int oparg, PyObject *lhs, PyObject *rhs) } Py_UNREACHABLE(); } -#endif +#endif // Py_STATS void _Py_Specialize_BinaryOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, @@ -2102,7 +2111,7 @@ compare_op_fail_kind(PyObject *lhs, PyObject *rhs) } return SPEC_FAIL_OTHER; } -#endif +#endif // Py_STATS void _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, @@ -2165,7 +2174,7 @@ unpack_sequence_fail_kind(PyObject *seq) } return SPEC_FAIL_OTHER; } -#endif +#endif // Py_STATS void _Py_Specialize_UnpackSequence(PyObject *seq, _Py_CODEUNIT *instr, int oparg) @@ -2206,7 +2215,6 @@ _Py_Specialize_UnpackSequence(PyObject *seq, _Py_CODEUNIT *instr, int oparg) } #ifdef Py_STATS - int _PySpecialization_ClassifyIterator(PyObject *iter) { @@ -2277,8 +2285,7 @@ int } return SPEC_FAIL_OTHER; } - -#endif +#endif // Py_STATS void _Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr, int oparg) @@ -2431,7 +2438,7 @@ _Py_Specialize_ToBool(PyObject *value, _Py_CODEUNIT *instr) goto failure; } SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_OTHER); -#endif +#endif // Py_STATS failure: STAT_INC(TO_BOOL, failure); instr->op.code = TO_BOOL; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 0ec763c7aa7cf85..deec606b635706a 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -30,6 +30,7 @@ Data members: #include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_pystats.h" // _Py_PrintSpecializationStats() #include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags() #include "pycore_sysmodule.h" // Define _PySys_GetSizeOf() #include "pycore_tuple.h" // _PyTuple_FromArray() @@ -2104,6 +2105,7 @@ sys_is_finalizing_impl(PyObject *module) return PyBool_FromLong(Py_IsFinalizing()); } + #ifdef Py_STATS /*[clinic input] sys._stats_on @@ -2115,7 +2117,7 @@ static PyObject * sys__stats_on_impl(PyObject *module) /*[clinic end generated code: output=aca53eafcbb4d9fe input=8ddc6df94e484f3a]*/ { - _py_stats = &_py_stats_struct; + _Py_StatsOn(); Py_RETURN_NONE; } @@ -2129,7 +2131,7 @@ static PyObject * sys__stats_off_impl(PyObject *module) /*[clinic end generated code: output=1534c1ee63812214 input=b3e50e71ecf29f66]*/ { - _py_stats = NULL; + _Py_StatsOff(); Py_RETURN_NONE; } @@ -2161,8 +2163,8 @@ sys__stats_dump_impl(PyObject *module) _Py_StatsClear(); Py_RETURN_NONE; } +#endif // Py_STATS -#endif #ifdef ANDROID_API_LEVEL /*[clinic input]