@@ -402,15 +402,64 @@ def show_results(self, stream=None):
402402 (function_name ,) + ts [0 ] + ts [1 ]))
403403
404404
405+ class CodeMap (dict ):
406+
407+ def __init__ (self , include_children ):
408+ self .include_children = include_children
409+ self ._toplevel = []
410+
411+ def add (self , code , toplevel_code = None ):
412+ if code in self :
413+ return
414+
415+ if toplevel_code is None :
416+ filename = code .co_filename
417+ if filename .endswith ((".pyc" , ".pyo" )):
418+ filename = filename [:- 1 ]
419+ if not os .path .exists (filename ):
420+ print ('ERROR: Could not find file ' + filename )
421+ if filename .startswith (("ipython-input" , "<ipython-input" )):
422+ print ("NOTE: %mprun can only be used on functions defined in "
423+ "physical files, and not in the IPython environment." )
424+ return
425+
426+ toplevel_code = code
427+ (sub_lines , start_line ) = inspect .getsourcelines (code )
428+ linenos = range (start_line ,
429+ start_line + len (sub_lines ))
430+ self ._toplevel .append ((filename , code , linenos ))
431+ self [code ] = {}
432+ else :
433+ self [code ] = self [toplevel_code ]
434+
435+ for subcode in filter (inspect .iscode , code .co_consts ):
436+ self .add (subcode , toplevel_code = toplevel_code )
437+
438+ def trace (self , code , lineno ):
439+ memory = _get_memory (- 1 , include_children = self .include_children )
440+ # if there is already a measurement for that line get the max
441+ previous_memory = self [code ].get (lineno , 0 )
442+ self [code ][lineno ] = max (memory , previous_memory )
443+
444+ def items (self ):
445+ """Iterate on the toplevel code blocks."""
446+ for (filename , code , linenos ) in self ._toplevel :
447+ measures = self [code ]
448+ if not measures :
449+ continue # skip if no measurement
450+ line_iterator = ((line , measures .get (line )) for line in linenos )
451+ yield (filename , line_iterator )
452+
453+
405454class LineProfiler (object ):
406455 """ A profiler that records the amount of memory for each line """
407456
408457 def __init__ (self , ** kw ):
409- self .code_map = {}
458+ include_children = kw .get ('include_children' , False )
459+ self .code_map = CodeMap (include_children = include_children )
410460 self .enable_count = 0
411461 self .max_mem = kw .get ('max_mem' , None )
412- self .prevline = None
413- self .include_children = kw .get ('include_children' , False )
462+ self .prevlines = []
414463
415464 def __call__ (self , func = None , precision = 1 ):
416465 if func is not None :
@@ -426,13 +475,6 @@ def inner_partial(f):
426475 return self .__call__ (f , precision = precision )
427476 return inner_partial
428477
429- def add_code (self , code , toplevel_code = None ):
430- if code not in self .code_map :
431- self .code_map [code ] = {}
432-
433- for subcode in filter (inspect .iscode , code .co_consts ):
434- self .add_code (subcode )
435-
436478 def add_function (self , func ):
437479 """ Record line profiling information for the given Python function.
438480 """
@@ -443,7 +485,7 @@ def add_function(self, func):
443485 warnings .warn ("Could not extract a code object for the object %r"
444486 % func )
445487 else :
446- self .add_code (code )
488+ self .code_map . add (code )
447489
448490 def wrap_function (self , func ):
449491 """ Wrap a function to profile it.
@@ -493,15 +535,15 @@ def disable_by_count(self):
493535
494536 def trace_memory_usage (self , frame , event , arg ):
495537 """Callback for sys.settrace"""
496- if (event in ('call' , 'line' , 'return' )
497- and frame .f_code in self .code_map ):
498- if event != 'call' :
538+ if frame .f_code in self .code_map :
539+ if event == 'call' :
499540 # "call" event just saves the lineno but not the memory
500- mem = _get_memory (- 1 , include_children = self .include_children )
501- # if there is already a measurement for that line get the max
502- old_mem = self .code_map [frame .f_code ].get (self .prevline , 0 )
503- self .code_map [frame .f_code ][self .prevline ] = max (mem , old_mem )
504- self .prevline = frame .f_lineno
541+ self .prevlines .append (frame .f_lineno )
542+ elif event == 'line' :
543+ self .code_map .trace (frame .f_code , self .prevlines [- 1 ])
544+ self .prevlines [- 1 ] = frame .f_lineno
545+ elif event == 'return' :
546+ self .code_map .trace (frame .f_code , self .prevlines .pop ())
505547
506548 if self ._original_trace_function is not None :
507549 (self ._original_trace_function )(frame , event , arg )
@@ -553,45 +595,28 @@ def show_results(prof, stream=None, precision=1):
553595 stream = sys .stdout
554596 template = '{0:>6} {1:>12} {2:>12} {3:<}'
555597
556- for code in prof .code_map :
557- lines = prof .code_map [code ]
558- if not lines :
559- # .. measurements are empty ..
560- continue
561- filename = code .co_filename
562- if filename .endswith ((".pyc" , ".pyo" )):
563- filename = filename [:- 1 ]
564- stream .write ('Filename: ' + filename + '\n \n ' )
565- if not os .path .exists (filename ):
566- stream .write ('ERROR: Could not find file ' + filename + '\n ' )
567- if any ([filename .startswith (k ) for k in
568- ("ipython-input" , "<ipython-input" )]):
569- print ("NOTE: %mprun can only be used on functions defined in "
570- "physical files, and not in the IPython environment." )
571- continue
572- all_lines = linecache .getlines (filename )
573- sub_lines = inspect .getblock (all_lines [code .co_firstlineno - 1 :])
574- linenos = range (code .co_firstlineno ,
575- code .co_firstlineno + len (sub_lines ))
576-
598+ for (filename , lines ) in prof .code_map .items ():
577599 header = template .format ('Line #' , 'Mem usage' , 'Increment' ,
578600 'Line Contents' )
601+
602+ stream .write ('Filename: ' + filename + '\n \n ' )
579603 stream .write (header + '\n ' )
580604 stream .write ('=' * len (header ) + '\n ' )
581605
582- mem_old = lines [min (lines .keys ())]
606+ all_lines = linecache .getlines (filename )
607+ mem_old = None
583608 float_format = '{0}.{1}f' .format (precision + 4 , precision )
584609 template_mem = '{0:' + float_format + '} MiB'
585- for line in linenos :
586- mem = ''
587- inc = ''
588- if line in lines :
589- mem = lines [line ]
590- inc = mem - mem_old
610+ for (lineno , mem ) in lines :
611+ if mem :
612+ inc = (mem - mem_old ) if mem_old else 0
591613 mem_old = mem
592614 mem = template_mem .format (mem )
593615 inc = template_mem .format (inc )
594- stream .write (template .format (line , mem , inc , all_lines [line - 1 ]))
616+ else :
617+ mem = ''
618+ inc = ''
619+ stream .write (template .format (lineno , mem , inc , all_lines [lineno - 1 ]))
595620 stream .write ('\n \n ' )
596621
597622
0 commit comments