mirror of
				https://github.com/python/cpython.git
				synced 2025-10-26 03:04:41 +00:00 
			
		
		
		
	
		
			
	
	
		
			130 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			130 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """
 | ||
|  | 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()) |