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