]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-44725 : expose specialization stats in python (GH-27192)
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Thu, 29 Jul 2021 16:26:53 +0000 (17:26 +0100)
committerGitHub <noreply@github.com>
Thu, 29 Jul 2021 16:26:53 +0000 (17:26 +0100)
Include/internal/pycore_code.h
Lib/opcode.py
Lib/test/test__opcode.py
Misc/NEWS.d/next/Core and Builtins/2021-07-23-15-17-01.bpo-44725.qcuKaa.rst [new file with mode: 0644]
Modules/_opcode.c
Modules/clinic/_opcode.c.h
Python/specialize.c

index bc469763670d4ee901ee57457093c84a14014cc9..2cd7c70650f9341205b96d3377fd98d2843dcc1c 100644 (file)
@@ -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
index 7ba15199b7f7c83a351d88ceb235dffb6b9280a9..7735848db0958dfae0cdebe5f06b30ce2410afd3 100644 (file)
@@ -234,3 +234,13 @@ _specialized_instructions = [
     "LOAD_GLOBAL_MODULE",
     "LOAD_GLOBAL_BUILTIN",
 ]
+
+_specialization_stats = [
+    "specialization_success",
+    "specialization_failure",
+    "hit",
+    "deferred",
+    "miss",
+    "deopt",
+    "unquickened",
+]
index 3bb64a798b3e3866798294cad9f5a5beb3051fdd..928198ae123b57ab6c4d0335cb0772ccb8dfb63c 100644 (file)
@@ -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 (file)
index 0000000..995cf14
--- /dev/null
@@ -0,0 +1 @@
+Expose specialization stats in python via :func:`_opcode.get_specialization_stats`.
\ No newline at end of file
index 609a2621d5d77e90723ab9ea05bf529a1a71c923..d440b5c51adc39a89b7a6fe835cef04878de2352 100644 (file)
@@ -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}
 };
 
index 6915f21d6444498a6fe1e7bf6aaf2e8d40b58fb2..6ef303bb05e7c63e7019cf0775c1da54ac21858b 100644 (file)
@@ -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]*/
index 5ebe596418b032a0996660fe2d71db327834f6cf..f699065b4c667c2ac8b09ea57493f306df943c19 100644 (file)
@@ -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)