""" | |
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()) |