diff options
author | Victor Stinner <vstinner@python.org> | 2023-09-06 15:54:59 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-06 15:54:59 (GMT) |
commit | a0773b89dfe5cd2190d539905dd89e7f6455668e (patch) | |
tree | e11c7306e6ed7750265eda6ba8fa49f329bc0f85 /Python | |
parent | 8ff11425783806f8cb78e99f667546b1f7f3428e (diff) | |
download | cpython-a0773b89dfe5cd2190d539905dd89e7f6455668e.zip cpython-a0773b89dfe5cd2190d539905dd89e7f6455668e.tar.gz cpython-a0773b89dfe5cd2190d539905dd89e7f6455668e.tar.bz2 |
gh-108753: Enhance pystats (#108754)
Statistics gathering is now off by default. Use the "-X pystats"
command line option or set the new PYTHONSTATS environment variable
to 1 to turn statistics gathering on at Python startup.
Statistics are no longer dumped at exit if statistics gathering was
off or statistics have been cleared.
Changes:
* Add PYTHONSTATS environment variable.
* sys._stats_dump() now returns False if statistics are not dumped
because they are all equal to zero.
* Add PyConfig._pystats member.
* Add tests on sys functions and on setting PyConfig._pystats to 1.
* Add Include/cpython/pystats.h and Include/internal/pycore_pystats.h
header files.
* Rename '_py_stats' variable to '_Py_stats'.
* Exclude Include/cpython/pystats.h from the Py_LIMITED_API.
* Move pystats.h include from object.h to Python.h.
* Add _Py_StatsOn() and _Py_StatsOff() functions. Remove
'_py_stats_struct' variable from the API: make it static in
specialize.c.
* Document API in Include/pystats.h and Include/cpython/pystats.h.
* Complete pystats documentation in Doc/using/configure.rst.
* Don't write "all zeros" stats: if _stats_off() and _stats_clear()
or _stats_dump() were called.
* _PyEval_Fini() now always call _Py_PrintSpecializationStats() which
does nothing if stats are all zeros.
Co-authored-by: Michael Droettboom <mdboom@gmail.com>
Diffstat (limited to 'Python')
-rw-r--r-- | Python/ceval_gil.c | 5 | ||||
-rw-r--r-- | Python/ceval_macros.h | 2 | ||||
-rw-r--r-- | Python/clinic/sysmodule.c.h | 24 | ||||
-rw-r--r-- | Python/initconfig.c | 34 | ||||
-rw-r--r-- | Python/specialize.c | 98 | ||||
-rw-r--r-- | Python/sysmodule.c | 28 |
6 files changed, 136 insertions, 55 deletions
diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 7c9ad07..e53ffa7 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: diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 4b7c444..81fbb79 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/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index a4b3987..30691c3 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1120,7 +1120,7 @@ PyDoc_STRVAR(sys__stats_on__doc__, "_stats_on($module, /)\n" "--\n" "\n" -"Turns on stats gathering (stats gathering is on by default)."); +"Turns on stats gathering (stats gathering is off by default)."); #define SYS__STATS_ON_METHODDEF \ {"_stats_on", (PyCFunction)sys__stats_on, METH_NOARGS, sys__stats_on__doc__}, @@ -1142,7 +1142,7 @@ PyDoc_STRVAR(sys__stats_off__doc__, "_stats_off($module, /)\n" "--\n" "\n" -"Turns off stats gathering (stats gathering is on by default)."); +"Turns off stats gathering (stats gathering is off by default)."); #define SYS__STATS_OFF_METHODDEF \ {"_stats_off", (PyCFunction)sys__stats_off, METH_NOARGS, sys__stats_off__doc__}, @@ -1186,18 +1186,30 @@ PyDoc_STRVAR(sys__stats_dump__doc__, "_stats_dump($module, /)\n" "--\n" "\n" -"Dump stats to file, and clears the stats."); +"Dump stats to file, and clears the stats.\n" +"\n" +"Return False if no statistics were not dumped because stats gathering was off."); #define SYS__STATS_DUMP_METHODDEF \ {"_stats_dump", (PyCFunction)sys__stats_dump, METH_NOARGS, sys__stats_dump__doc__}, -static PyObject * +static int sys__stats_dump_impl(PyObject *module); static PyObject * sys__stats_dump(PyObject *module, PyObject *Py_UNUSED(ignored)) { - return sys__stats_dump_impl(module); + PyObject *return_value = NULL; + int _return_value; + + _return_value = sys__stats_dump_impl(module); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; } #endif /* defined(Py_STATS) */ @@ -1411,4 +1423,4 @@ exit: #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=6619682ea70e7375 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=549bb1f92a15f916 input=a9049054013a1b77]*/ diff --git a/Python/initconfig.c b/Python/initconfig.c index f9c5c64..a0467f5 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 "<prefix>\\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/specialize.c b/Python/specialize.c index aa40b33..e072167 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -13,17 +13,17 @@ #include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() #include "pycore_runtime.h" // _Py_ID() - #include <stdlib.h> // 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 }; +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,7 +216,8 @@ 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); @@ -226,18 +225,56 @@ print_stats(FILE *out, PyStats *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 }; + memset(&_py_gc_stats, 0, sizeof(_py_gc_stats)); + memset(&_Py_stats_struct, 0, sizeof(_Py_stats_struct)); + _Py_stats_struct.gc_stats = _py_gc_stats; +} + +static int +mem_is_zero(unsigned char *ptr, size_t size) +{ + for (size_t i=0; i < size; i++) { + if (*ptr != 0) { + return 0; + } + ptr++; } - _py_stats_struct = (PyStats) { 0 }; - _py_stats_struct.gc_stats = _py_gc_stats; + return 1; } -void +int _Py_PrintSpecializationStats(int to_file) { + PyStats *stats = &_Py_stats_struct; +#define MEM_IS_ZERO(DATA) mem_is_zero((unsigned char*)DATA, sizeof(*(DATA))) + int is_zero = ( + MEM_IS_ZERO(stats->gc_stats) // is a pointer + && MEM_IS_ZERO(&stats->opcode_stats) + && MEM_IS_ZERO(&stats->call_stats) + && MEM_IS_ZERO(&stats->object_stats) + ); +#undef MEM_IS_ZERO + if (is_zero) { + // gh-108753: -X pystats command line was used, but then _stats_off() + // and _stats_clear() have been called: in this case, avoid printing + // useless "all zeros" statistics. + return 0; + } + FILE *out = stderr; if (to_file) { /* Write to a file instead of stderr. */ @@ -268,26 +305,25 @@ _Py_PrintSpecializationStats(int to_file) else { fprintf(out, "Specialization stats:\n"); } - print_stats(out, &_py_stats_struct); + print_stats(out, stats); if (out != stderr) { fclose(out); } + return 1; } -#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 +1103,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 +1342,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 +1581,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 +1726,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 +1907,7 @@ call_fail_kind(PyObject *callable) } return SPEC_FAIL_OTHER; } -#endif +#endif // Py_STATS /* TODO: @@ -1995,7 +2031,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 +2138,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 +2201,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 +2242,6 @@ success: } #ifdef Py_STATS - int _PySpecialization_ClassifyIterator(PyObject *iter) { @@ -2277,8 +2312,7 @@ int } return SPEC_FAIL_OTHER; } - -#endif +#endif // Py_STATS void _Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr, int oparg) @@ -2431,7 +2465,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 3835f76..fed1281 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" // export _PySys_GetSizeOf() #include "pycore_tuple.h" // _PyTuple_FromArray() @@ -2106,32 +2107,33 @@ sys_is_finalizing_impl(PyObject *module) return PyBool_FromLong(Py_IsFinalizing()); } + #ifdef Py_STATS /*[clinic input] sys._stats_on -Turns on stats gathering (stats gathering is on by default). +Turns on stats gathering (stats gathering is off by default). [clinic start generated code]*/ static PyObject * sys__stats_on_impl(PyObject *module) -/*[clinic end generated code: output=aca53eafcbb4d9fe input=8ddc6df94e484f3a]*/ +/*[clinic end generated code: output=aca53eafcbb4d9fe input=43b5bfe145299e55]*/ { - _py_stats = &_py_stats_struct; + _Py_StatsOn(); Py_RETURN_NONE; } /*[clinic input] sys._stats_off -Turns off stats gathering (stats gathering is on by default). +Turns off stats gathering (stats gathering is off by default). [clinic start generated code]*/ static PyObject * sys__stats_off_impl(PyObject *module) -/*[clinic end generated code: output=1534c1ee63812214 input=b3e50e71ecf29f66]*/ +/*[clinic end generated code: output=1534c1ee63812214 input=d1a84c60c56cbce2]*/ { - _py_stats = NULL; + _Py_StatsOff(); Py_RETURN_NONE; } @@ -2150,21 +2152,23 @@ sys__stats_clear_impl(PyObject *module) } /*[clinic input] -sys._stats_dump +sys._stats_dump -> bool Dump stats to file, and clears the stats. + +Return False if no statistics were not dumped because stats gathering was off. [clinic start generated code]*/ -static PyObject * +static int sys__stats_dump_impl(PyObject *module) -/*[clinic end generated code: output=79f796fb2b4ddf05 input=92346f16d64f6f95]*/ +/*[clinic end generated code: output=6e346b4ba0de4489 input=31a489e39418b2a5]*/ { - _Py_PrintSpecializationStats(1); + int res = _Py_PrintSpecializationStats(1); _Py_StatsClear(); - Py_RETURN_NONE; + return res; } +#endif // Py_STATS -#endif #ifdef ANDROID_API_LEVEL /*[clinic input] |