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