diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index bc469763670..2cd7c70650f 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -321,6 +321,9 @@ typedef struct _stats { extern SpecializationStats _specialization_stats[256]; #define STAT_INC(opname, name) _specialization_stats[opname].name++ void _Py_PrintSpecializationStats(void); + +PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void); + #else #define STAT_INC(opname, name) ((void)0) #endif diff --git a/Lib/opcode.py b/Lib/opcode.py index 7ba15199b7f..7735848db09 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -234,3 +234,13 @@ _specialized_instructions = [ "LOAD_GLOBAL_MODULE", "LOAD_GLOBAL_BUILTIN", ] + +_specialization_stats = [ + "specialization_success", + "specialization_failure", + "hit", + "deferred", + "miss", + "deopt", + "unquickened", +] diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 3bb64a798b3..928198ae123 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -1,6 +1,7 @@ import dis from test.support.import_helper import import_module import unittest +import opcode _opcode = import_module("_opcode") from _opcode import stack_effect @@ -64,5 +65,31 @@ class OpcodeTests(unittest.TestCase): self.assertEqual(nojump, common) +class SpecializationStatsTests(unittest.TestCase): + def test_specialization_stats(self): + stat_names = opcode._specialization_stats + + specialized_opcodes = [ + op[:-len("_ADAPTIVE")].lower() for + op in opcode._specialized_instructions + if op.endswith("_ADAPTIVE")] + self.assertIn('load_attr', specialized_opcodes) + self.assertIn('binary_subscr', specialized_opcodes) + + stats = _opcode.get_specialization_stats() + if stats is not None: + self.assertIsInstance(stats, dict) + self.assertCountEqual(stats.keys(), specialized_opcodes) + self.assertCountEqual( + stats['load_attr'].keys(), + stat_names + ['fails']) + for sn in stat_names: + self.assertIsInstance(stats['load_attr'][sn], int) + self.assertIsInstance(stats['load_attr']['fails'], dict) + for k,v in stats['load_attr']['fails'].items(): + self.assertIsInstance(k, tuple) + self.assertIsInstance(v, int) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-23-15-17-01.bpo-44725.qcuKaa.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-23-15-17-01.bpo-44725.qcuKaa.rst new file mode 100644 index 00000000000..995cf148001 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-23-15-17-01.bpo-44725.qcuKaa.rst @@ -0,0 +1 @@ +Expose specialization stats in python via :func:`_opcode.get_specialization_stats`. \ No newline at end of file diff --git a/Modules/_opcode.c b/Modules/_opcode.c index 609a2621d5d..d440b5c51ad 100644 --- a/Modules/_opcode.c +++ b/Modules/_opcode.c @@ -1,5 +1,6 @@ #include "Python.h" #include "opcode.h" +#include "internal/pycore_code.h" /*[clinic input] module _opcode @@ -73,9 +74,28 @@ _opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg, return effect; } +/*[clinic input] + +_opcode.get_specialization_stats + +Return the specialization stats +[clinic start generated code]*/ + +static PyObject * +_opcode_get_specialization_stats_impl(PyObject *module) +/*[clinic end generated code: output=fcbc32fdfbec5c17 input=e1f60db68d8ce5f6]*/ +{ +#if SPECIALIZATION_STATS + return _Py_GetSpecializationStats(); +#else + Py_RETURN_NONE; +#endif +} + static PyMethodDef opcode_functions[] = { _OPCODE_STACK_EFFECT_METHODDEF + _OPCODE_GET_SPECIALIZATION_STATS_METHODDEF {NULL, NULL, 0, NULL} }; diff --git a/Modules/clinic/_opcode.c.h b/Modules/clinic/_opcode.c.h index 6915f21d644..6ef303bb05e 100644 --- a/Modules/clinic/_opcode.c.h +++ b/Modules/clinic/_opcode.c.h @@ -56,4 +56,22 @@ skip_optional_kwonly: exit: return return_value; } -/*[clinic end generated code: output=bcf66d25c2624197 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_opcode_get_specialization_stats__doc__, +"get_specialization_stats($module, /)\n" +"--\n" +"\n" +"Return the specialization stats"); + +#define _OPCODE_GET_SPECIALIZATION_STATS_METHODDEF \ + {"get_specialization_stats", (PyCFunction)_opcode_get_specialization_stats, METH_NOARGS, _opcode_get_specialization_stats__doc__}, + +static PyObject * +_opcode_get_specialization_stats_impl(PyObject *module); + +static PyObject * +_opcode_get_specialization_stats(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _opcode_get_specialization_stats_impl(module); +} +/*[clinic end generated code: output=1699b4b1488b49c1 input=a9049054013a1b77]*/ diff --git a/Python/specialize.c b/Python/specialize.c index 5ebe596418b..f699065b4c6 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -40,6 +40,83 @@ Py_ssize_t _Py_QuickenedCount = 0; #if SPECIALIZATION_STATS SpecializationStats _specialization_stats[256] = { 0 }; +#define ADD_STAT_TO_DICT(res, field) \ + do { \ + PyObject *val = PyLong_FromUnsignedLongLong(stats->field); \ + if (val == NULL) { \ + Py_DECREF(res); \ + return NULL; \ + } \ + if (PyDict_SetItemString(res, #field, val) == -1) { \ + Py_DECREF(res); \ + Py_DECREF(val); \ + return NULL; \ + } \ + Py_DECREF(val); \ + } while(0); + +static PyObject* +stats_to_dict(SpecializationStats *stats) +{ + PyObject *res = PyDict_New(); + if (res == NULL) { + return NULL; + } + ADD_STAT_TO_DICT(res, specialization_success); + ADD_STAT_TO_DICT(res, specialization_failure); + ADD_STAT_TO_DICT(res, hit); + ADD_STAT_TO_DICT(res, deferred); + ADD_STAT_TO_DICT(res, miss); + ADD_STAT_TO_DICT(res, deopt); + ADD_STAT_TO_DICT(res, unquickened); +#if SPECIALIZATION_STATS_DETAILED + if (stats->miss_types != NULL) { + if (PyDict_SetItemString(res, "fails", stats->miss_types) == -1) { + Py_DECREF(res); + return NULL; + } + } +#endif + return res; +} +#undef ADD_STAT_TO_DICT + +static int +add_stat_dict( + PyObject *res, + int opcode, + const char *name) { + + SpecializationStats *stats = &_specialization_stats[opcode]; + PyObject *d = stats_to_dict(stats); + if (d == NULL) { + return -1; + } + int err = PyDict_SetItemString(res, name, d); + Py_DECREF(d); + return err; +} + +#if SPECIALIZATION_STATS +PyObject* +_Py_GetSpecializationStats(void) { + PyObject *stats = PyDict_New(); + if (stats == NULL) { + return NULL; + } + int err = 0; + err += add_stat_dict(stats, LOAD_ATTR, "load_attr"); + err += add_stat_dict(stats, LOAD_GLOBAL, "load_global"); + err += add_stat_dict(stats, BINARY_SUBSCR, "binary_subscr"); + if (err < 0) { + Py_DECREF(stats); + return NULL; + } + return stats; +} +#endif + + #define PRINT_STAT(name, field) fprintf(stderr, " %s." #field " : %" PRIu64 "\n", name, stats->field); static void @@ -71,6 +148,7 @@ print_stats(SpecializationStats *stats, const char *name) } #endif } +#undef PRINT_STAT void _Py_PrintSpecializationStats(void)