diff options
Diffstat (limited to 'Tools/scripts')
-rw-r--r-- | Tools/scripts/README | 1 | ||||
-rw-r--r-- | Tools/scripts/analyze_dxp.py | 129 |
2 files changed, 130 insertions, 0 deletions
diff --git a/Tools/scripts/README b/Tools/scripts/README index f5c9791..64fc2bd 100644 --- a/Tools/scripts/README +++ b/Tools/scripts/README @@ -4,6 +4,7 @@ dutree or lll) are also generally useful UNIX tools. See also the Demo/scripts directory! +analyze_dxp.py Analyzes the result of sys.getdxp() byext.py Print lines/words/chars stats of files by extension byteyears.py Print product of a file's size and age checkappend.py Search for multi-argument .append() calls diff --git a/Tools/scripts/analyze_dxp.py b/Tools/scripts/analyze_dxp.py new file mode 100644 index 0000000..bde931e --- /dev/null +++ b/Tools/scripts/analyze_dxp.py @@ -0,0 +1,129 @@ +""" +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()) |