1 | #!/usr/bin/env python
|
---|
2 | """
|
---|
3 | Some helper functions to analyze the output of sys.getdxp() (which is
|
---|
4 | only available if Python was built with -DDYNAMIC_EXECUTION_PROFILE).
|
---|
5 | These will tell you which opcodes have been executed most frequently
|
---|
6 | in the current process, and, if Python was also built with -DDXPAIRS,
|
---|
7 | will tell you which instruction _pairs_ were executed most frequently,
|
---|
8 | which may help in choosing new instructions.
|
---|
9 |
|
---|
10 | If Python was built without -DDYNAMIC_EXECUTION_PROFILE, importing
|
---|
11 | this module will raise a RuntimeError.
|
---|
12 |
|
---|
13 | If you're running a script you want to profile, a simple way to get
|
---|
14 | the common pairs is:
|
---|
15 |
|
---|
16 | $ PYTHONPATH=$PYTHONPATH:<python_srcdir>/Tools/scripts \
|
---|
17 | ./python -i -O the_script.py --args
|
---|
18 | ...
|
---|
19 | > from analyze_dxp import *
|
---|
20 | > s = render_common_pairs()
|
---|
21 | > open('/tmp/some_file', 'w').write(s)
|
---|
22 | """
|
---|
23 |
|
---|
24 | import copy
|
---|
25 | import opcode
|
---|
26 | import operator
|
---|
27 | import sys
|
---|
28 | import threading
|
---|
29 |
|
---|
30 | if not hasattr(sys, "getdxp"):
|
---|
31 | raise RuntimeError("Can't import analyze_dxp: Python built without"
|
---|
32 | " -DDYNAMIC_EXECUTION_PROFILE.")
|
---|
33 |
|
---|
34 |
|
---|
35 | _profile_lock = threading.RLock()
|
---|
36 | _cumulative_profile = sys.getdxp()
|
---|
37 |
|
---|
38 | # If Python was built with -DDXPAIRS, sys.getdxp() returns a list of
|
---|
39 | # lists of ints. Otherwise it returns just a list of ints.
|
---|
40 | def has_pairs(profile):
|
---|
41 | """Returns True if the Python that produced the argument profile
|
---|
42 | was built with -DDXPAIRS."""
|
---|
43 |
|
---|
44 | return len(profile) > 0 and isinstance(profile[0], list)
|
---|
45 |
|
---|
46 |
|
---|
47 | def reset_profile():
|
---|
48 | """Forgets any execution profile that has been gathered so far."""
|
---|
49 | with _profile_lock:
|
---|
50 | sys.getdxp() # Resets the internal profile
|
---|
51 | global _cumulative_profile
|
---|
52 | _cumulative_profile = sys.getdxp() # 0s out our copy.
|
---|
53 |
|
---|
54 |
|
---|
55 | def merge_profile():
|
---|
56 | """Reads sys.getdxp() and merges it into this module's cached copy.
|
---|
57 |
|
---|
58 | We need this because sys.getdxp() 0s itself every time it's called."""
|
---|
59 |
|
---|
60 | with _profile_lock:
|
---|
61 | new_profile = sys.getdxp()
|
---|
62 | if has_pairs(new_profile):
|
---|
63 | for first_inst in range(len(_cumulative_profile)):
|
---|
64 | for second_inst in range(len(_cumulative_profile[first_inst])):
|
---|
65 | _cumulative_profile[first_inst][second_inst] += (
|
---|
66 | new_profile[first_inst][second_inst])
|
---|
67 | else:
|
---|
68 | for inst in range(len(_cumulative_profile)):
|
---|
69 | _cumulative_profile[inst] += new_profile[inst]
|
---|
70 |
|
---|
71 |
|
---|
72 | def snapshot_profile():
|
---|
73 | """Returns the cumulative execution profile until this call."""
|
---|
74 | with _profile_lock:
|
---|
75 | merge_profile()
|
---|
76 | return copy.deepcopy(_cumulative_profile)
|
---|
77 |
|
---|
78 |
|
---|
79 | def common_instructions(profile):
|
---|
80 | """Returns the most common opcodes in order of descending frequency.
|
---|
81 |
|
---|
82 | The result is a list of tuples of the form
|
---|
83 | (opcode, opname, # of occurrences)
|
---|
84 |
|
---|
85 | """
|
---|
86 | if has_pairs(profile) and profile:
|
---|
87 | inst_list = profile[-1]
|
---|
88 | else:
|
---|
89 | inst_list = profile
|
---|
90 | result = [(op, opcode.opname[op], count)
|
---|
91 | for op, count in enumerate(inst_list)
|
---|
92 | if count > 0]
|
---|
93 | result.sort(key=operator.itemgetter(2), reverse=True)
|
---|
94 | return result
|
---|
95 |
|
---|
96 |
|
---|
97 | def common_pairs(profile):
|
---|
98 | """Returns the most common opcode pairs in order of descending frequency.
|
---|
99 |
|
---|
100 | The result is a list of tuples of the form
|
---|
101 | ((1st opcode, 2nd opcode),
|
---|
102 | (1st opname, 2nd opname),
|
---|
103 | # of occurrences of the pair)
|
---|
104 |
|
---|
105 | """
|
---|
106 | if not has_pairs(profile):
|
---|
107 | return []
|
---|
108 | result = [((op1, op2), (opcode.opname[op1], opcode.opname[op2]), count)
|
---|
109 | # Drop the row of single-op profiles with [:-1]
|
---|
110 | for op1, op1profile in enumerate(profile[:-1])
|
---|
111 | for op2, count in enumerate(op1profile)
|
---|
112 | if count > 0]
|
---|
113 | result.sort(key=operator.itemgetter(2), reverse=True)
|
---|
114 | return result
|
---|
115 |
|
---|
116 |
|
---|
117 | def render_common_pairs(profile=None):
|
---|
118 | """Renders the most common opcode pairs to a string in order of
|
---|
119 | descending frequency.
|
---|
120 |
|
---|
121 | The result is a series of lines of the form:
|
---|
122 | # of occurrences: ('1st opname', '2nd opname')
|
---|
123 |
|
---|
124 | """
|
---|
125 | if profile is None:
|
---|
126 | profile = snapshot_profile()
|
---|
127 | def seq():
|
---|
128 | for _, ops, count in common_pairs(profile):
|
---|
129 | yield "%s: %s\n" % (count, ops)
|
---|
130 | return ''.join(seq())
|
---|