/Tools/scripts/analyze_dxp.py

http://unladen-swallow.googlecode.com/ · Python · 165 lines · 143 code · 2 blank · 20 comment · 0 complexity · df24caa7c811b61ffcdb8ab626942d4c MD5 · raw file

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