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.
This commit is contained in:
Victor Stinner 2022-10-04 15:28:57 +02:00 committed by GitHub
parent 6e53308829
commit 116fa62c6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 7 additions and 201 deletions

View File

@ -25,7 +25,7 @@ class TestSundryScripts(unittest.TestCase):
# scripts that use windows-only modules # scripts that use windows-only modules
windows_only = ['win_add2path'] windows_only = ['win_add2path']
# denylisted for other reasons # denylisted for other reasons
other = ['analyze_dxp', '2to3'] other = ['2to3']
skiplist = denylist + allowlist + windows_only + other skiplist = denylist + allowlist + windows_only + other
@ -50,13 +50,6 @@ def test_sundry_windows(self):
for name in self.windows_only: for name in self.windows_only:
import_tool(name) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -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.

View File

@ -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 Py_ssize_t
_PyEval_RequestCodeExtraIndex(freefunc free) _PyEval_RequestCodeExtraIndex(freefunc free)

View File

@ -2014,11 +2014,6 @@ sys__debugmallocstats_impl(PyObject *module)
extern PyObject *_Py_GetObjects(PyObject *, PyObject *); extern PyObject *_Py_GetObjects(PyObject *, PyObject *);
#endif #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 #ifdef __cplusplus
} }
#endif #endif
@ -2217,9 +2212,6 @@ static PyMethodDef sys_methods[] = {
SYS_GETDEFAULTENCODING_METHODDEF SYS_GETDEFAULTENCODING_METHODDEF
SYS_GETDLOPENFLAGS_METHODDEF SYS_GETDLOPENFLAGS_METHODDEF
SYS_GETALLOCATEDBLOCKS_METHODDEF SYS_GETALLOCATEDBLOCKS_METHODDEF
#ifdef Py_STATS
{"getdxp", _Py_GetDXProfile, METH_VARARGS},
#endif
SYS_GETFILESYSTEMENCODING_METHODDEF SYS_GETFILESYSTEMENCODING_METHODDEF
SYS_GETFILESYSTEMENCODEERRORS_METHODDEF SYS_GETFILESYSTEMENCODEERRORS_METHODDEF
SYS__GETQUICKENEDCOUNT_METHODDEF SYS__GETQUICKENEDCOUNT_METHODDEF

View File

@ -3,7 +3,6 @@ useful while building, extending or managing Python.
2to3 Main script for running the 2to3 conversion tool 2to3 Main script for running the 2to3 conversion tool
abitype.py Converts a C file to use the PEP 384 type definition API 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 combinerefs.py A helper for analyzing PYTHONDUMPREFS output
diff.py Print file diffs in context, unified, or ndiff formats diff.py Print file diffs in context, unified, or ndiff formats
eptags.py Create Emacs TAGS file for Python modules eptags.py Create Emacs TAGS file for Python modules

View File

@ -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:<python_srcdir>/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())