From 116fa62c6ee18e2b2ccf3697802034c0d13a16e8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Oct 2022 15:28:57 +0200 Subject: gh-97670: Remove sys.getdxp() and analyze_dxp.py script (#97671) Remove the sys.getdxp() function and the Tools/scripts/analyze_dxp.py script. DXP stands for "dynamic execution pairs". They were related to DYNAMIC_EXECUTION_PROFILE and DXPAIRS macros which have been removed in Python 3.11. Python can now be built with "./configure --enable-pystats" to gather statistics on Python opcodes. --- Lib/test/test_tools/test_sundry.py | 9 +- .../2022-09-30-13-26-58.gh-issue-97670.n61vMR.rst | 6 + Python/ceval.c | 55 --------- Python/sysmodule.c | 8 -- Tools/scripts/README | 1 - Tools/scripts/analyze_dxp.py | 129 --------------------- 6 files changed, 7 insertions(+), 201 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-09-30-13-26-58.gh-issue-97670.n61vMR.rst delete mode 100644 Tools/scripts/analyze_dxp.py diff --git a/Lib/test/test_tools/test_sundry.py b/Lib/test/test_tools/test_sundry.py index 52369ec..ed03c2f 100644 --- a/Lib/test/test_tools/test_sundry.py +++ b/Lib/test/test_tools/test_sundry.py @@ -25,7 +25,7 @@ class TestSundryScripts(unittest.TestCase): # scripts that use windows-only modules windows_only = ['win_add2path'] # denylisted for other reasons - other = ['analyze_dxp', '2to3'] + other = ['2to3'] skiplist = denylist + allowlist + windows_only + other @@ -50,13 +50,6 @@ class TestSundryScripts(unittest.TestCase): for name in self.windows_only: import_tool(name) - def test_analyze_dxp_import(self): - if hasattr(sys, 'getdxp'): - import_tool('analyze_dxp') - else: - with self.assertRaises(RuntimeError): - import_tool('analyze_dxp') - if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-09-30-13-26-58.gh-issue-97670.n61vMR.rst b/Misc/NEWS.d/next/Core and Builtins/2022-09-30-13-26-58.gh-issue-97670.n61vMR.rst new file mode 100644 index 0000000..50b4787 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-09-30-13-26-58.gh-issue-97670.n61vMR.rst @@ -0,0 +1,6 @@ +Remove the :func:`sys.getdxp` function and the ``Tools/scripts/analyze_dxp.py`` +script. DXP stands for "dynamic execution pairs". They were related to +``DYNAMIC_EXECUTION_PROFILE`` and ``DXPAIRS`` macros which have been removed in +Python 3.11. Python can now be built with :option:`./configure --enable-pystats +<--enable-pystats>` to gather statistics on Python opcodes. Patch by Victor +Stinner. diff --git a/Python/ceval.c b/Python/ceval.c index 9453770..82b5422 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -7207,61 +7207,6 @@ format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int oparg) } } -#ifdef Py_STATS - -static PyObject * -getarray(uint64_t a[256]) -{ - int i; - PyObject *l = PyList_New(256); - if (l == NULL) return NULL; - for (i = 0; i < 256; i++) { - PyObject *x = PyLong_FromUnsignedLongLong(a[i]); - if (x == NULL) { - Py_DECREF(l); - return NULL; - } - PyList_SET_ITEM(l, i, x); - } - for (i = 0; i < 256; i++) - a[i] = 0; - return l; -} - -PyObject * -_Py_GetDXProfile(PyObject *self, PyObject *args) -{ - int i; - PyObject *l = PyList_New(257); - if (l == NULL) return NULL; - for (i = 0; i < 256; i++) { - PyObject *x = getarray(_py_stats_struct.opcode_stats[i].pair_count); - if (x == NULL) { - Py_DECREF(l); - return NULL; - } - PyList_SET_ITEM(l, i, x); - } - PyObject *counts = PyList_New(256); - if (counts == NULL) { - Py_DECREF(l); - return NULL; - } - for (i = 0; i < 256; i++) { - PyObject *x = PyLong_FromUnsignedLongLong( - _py_stats_struct.opcode_stats[i].execution_count); - if (x == NULL) { - Py_DECREF(counts); - Py_DECREF(l); - return NULL; - } - PyList_SET_ITEM(counts, i, x); - } - PyList_SET_ITEM(l, 256, counts); - return l; -} - -#endif Py_ssize_t _PyEval_RequestCodeExtraIndex(freefunc free) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 584a8be..1ecf6a2 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2014,11 +2014,6 @@ sys__debugmallocstats_impl(PyObject *module) extern PyObject *_Py_GetObjects(PyObject *, PyObject *); #endif -#ifdef Py_STATS -/* Defined in ceval.c because it uses static globals in that file */ -extern PyObject *_Py_GetDXProfile(PyObject *, PyObject *); -#endif - #ifdef __cplusplus } #endif @@ -2217,9 +2212,6 @@ static PyMethodDef sys_methods[] = { SYS_GETDEFAULTENCODING_METHODDEF SYS_GETDLOPENFLAGS_METHODDEF SYS_GETALLOCATEDBLOCKS_METHODDEF -#ifdef Py_STATS - {"getdxp", _Py_GetDXProfile, METH_VARARGS}, -#endif SYS_GETFILESYSTEMENCODING_METHODDEF SYS_GETFILESYSTEMENCODEERRORS_METHODDEF SYS__GETQUICKENEDCOUNT_METHODDEF diff --git a/Tools/scripts/README b/Tools/scripts/README index 6affa67..b53b0f2 100644 --- a/Tools/scripts/README +++ b/Tools/scripts/README @@ -3,7 +3,6 @@ useful while building, extending or managing Python. 2to3 Main script for running the 2to3 conversion tool abitype.py Converts a C file to use the PEP 384 type definition API -analyze_dxp.py Analyzes the result of sys.getdxp() combinerefs.py A helper for analyzing PYTHONDUMPREFS output diff.py Print file diffs in context, unified, or ndiff formats eptags.py Create Emacs TAGS file for Python modules diff --git a/Tools/scripts/analyze_dxp.py b/Tools/scripts/analyze_dxp.py deleted file mode 100644 index bde931e..0000000 --- a/Tools/scripts/analyze_dxp.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -Some helper functions to analyze the output of sys.getdxp() (which is -only available if Python was built with -DDYNAMIC_EXECUTION_PROFILE). -These will tell you which opcodes have been executed most frequently -in the current process, and, if Python was also built with -DDXPAIRS, -will tell you which instruction _pairs_ were executed most frequently, -which may help in choosing new instructions. - -If Python was built without -DDYNAMIC_EXECUTION_PROFILE, importing -this module will raise a RuntimeError. - -If you're running a script you want to profile, a simple way to get -the common pairs is: - -$ PYTHONPATH=$PYTHONPATH:/Tools/scripts \ -./python -i -O the_script.py --args -... -> from analyze_dxp import * -> s = render_common_pairs() -> open('/tmp/some_file', 'w').write(s) -""" - -import copy -import opcode -import operator -import sys -import threading - -if not hasattr(sys, "getdxp"): - raise RuntimeError("Can't import analyze_dxp: Python built without" - " -DDYNAMIC_EXECUTION_PROFILE.") - - -_profile_lock = threading.RLock() -_cumulative_profile = sys.getdxp() - -# If Python was built with -DDXPAIRS, sys.getdxp() returns a list of -# lists of ints. Otherwise it returns just a list of ints. -def has_pairs(profile): - """Returns True if the Python that produced the argument profile - was built with -DDXPAIRS.""" - - return len(profile) > 0 and isinstance(profile[0], list) - - -def reset_profile(): - """Forgets any execution profile that has been gathered so far.""" - with _profile_lock: - sys.getdxp() # Resets the internal profile - global _cumulative_profile - _cumulative_profile = sys.getdxp() # 0s out our copy. - - -def merge_profile(): - """Reads sys.getdxp() and merges it into this module's cached copy. - - We need this because sys.getdxp() 0s itself every time it's called.""" - - with _profile_lock: - new_profile = sys.getdxp() - if has_pairs(new_profile): - for first_inst in range(len(_cumulative_profile)): - for second_inst in range(len(_cumulative_profile[first_inst])): - _cumulative_profile[first_inst][second_inst] += ( - new_profile[first_inst][second_inst]) - else: - for inst in range(len(_cumulative_profile)): - _cumulative_profile[inst] += new_profile[inst] - - -def snapshot_profile(): - """Returns the cumulative execution profile until this call.""" - with _profile_lock: - merge_profile() - return copy.deepcopy(_cumulative_profile) - - -def common_instructions(profile): - """Returns the most common opcodes in order of descending frequency. - - The result is a list of tuples of the form - (opcode, opname, # of occurrences) - - """ - if has_pairs(profile) and profile: - inst_list = profile[-1] - else: - inst_list = profile - result = [(op, opcode.opname[op], count) - for op, count in enumerate(inst_list) - if count > 0] - result.sort(key=operator.itemgetter(2), reverse=True) - return result - - -def common_pairs(profile): - """Returns the most common opcode pairs in order of descending frequency. - - The result is a list of tuples of the form - ((1st opcode, 2nd opcode), - (1st opname, 2nd opname), - # of occurrences of the pair) - - """ - if not has_pairs(profile): - return [] - result = [((op1, op2), (opcode.opname[op1], opcode.opname[op2]), count) - # Drop the row of single-op profiles with [:-1] - for op1, op1profile in enumerate(profile[:-1]) - for op2, count in enumerate(op1profile) - if count > 0] - result.sort(key=operator.itemgetter(2), reverse=True) - return result - - -def render_common_pairs(profile=None): - """Renders the most common opcode pairs to a string in order of - descending frequency. - - The result is a series of lines of the form: - # of occurrences: ('1st opname', '2nd opname') - - """ - if profile is None: - profile = snapshot_profile() - def seq(): - for _, ops, count in common_pairs(profile): - yield "%s: %s\n" % (count, ops) - return ''.join(seq()) -- cgit v0.12