mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#! /usr/bin/env python3
 | 
						|
 | 
						|
"""Tool for measuring execution time of small code snippets.
 | 
						|
 | 
						|
This module avoids a number of common traps for measuring execution
 | 
						|
times.  See also Tim Peters' introduction to the Algorithms chapter in
 | 
						|
the Python Cookbook, published by O'Reilly.
 | 
						|
 | 
						|
Library usage: see the Timer class.
 | 
						|
 | 
						|
Command line usage:
 | 
						|
    python timeit.py [-n N] [-r N] [-s S] [-t] [-c] [-p] [-h] [--] [statement]
 | 
						|
 | 
						|
Options:
 | 
						|
  -n/--number N: how many times to execute 'statement' (default: see below)
 | 
						|
  -r/--repeat N: how many times to repeat the timer (default 3)
 | 
						|
  -s/--setup S: statement to be executed once initially (default 'pass').
 | 
						|
                Execution time of this setup statement is NOT timed.
 | 
						|
  -p/--process: use time.process_time() (default is time.perf_counter())
 | 
						|
  -t/--time: use time.time() (deprecated)
 | 
						|
  -c/--clock: use time.clock() (deprecated)
 | 
						|
  -v/--verbose: print raw timing results; repeat for more digits precision
 | 
						|
  -u/--unit: set the output time unit (usec, msec, or sec)
 | 
						|
  -h/--help: print this usage message and exit
 | 
						|
  --: separate options from statement, use when statement starts with -
 | 
						|
  statement: statement to be timed (default 'pass')
 | 
						|
 | 
						|
A multi-line statement may be given by specifying each line as a
 | 
						|
separate argument; indented lines are possible by enclosing an
 | 
						|
argument in quotes and using leading spaces.  Multiple -s options are
 | 
						|
treated similarly.
 | 
						|
 | 
						|
If -n is not given, a suitable number of loops is calculated by trying
 | 
						|
successive powers of 10 until the total time is at least 0.2 seconds.
 | 
						|
 | 
						|
Note: there is a certain baseline overhead associated with executing a
 | 
						|
pass statement.  It differs between versions.  The code here doesn't try
 | 
						|
to hide it, but you should be aware of it.  The baseline overhead can be
 | 
						|
measured by invoking the program without arguments.
 | 
						|
 | 
						|
Classes:
 | 
						|
 | 
						|
    Timer
 | 
						|
 | 
						|
Functions:
 | 
						|
 | 
						|
    timeit(string, string) -> float
 | 
						|
    repeat(string, string) -> list
 | 
						|
    default_timer() -> float
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
import gc
 | 
						|
import sys
 | 
						|
import time
 | 
						|
import itertools
 | 
						|
 | 
						|
__all__ = ["Timer", "timeit", "repeat", "default_timer"]
 | 
						|
 | 
						|
dummy_src_name = "<timeit-src>"
 | 
						|
default_number = 1000000
 | 
						|
default_repeat = 3
 | 
						|
default_timer = time.perf_counter
 | 
						|
 | 
						|
_globals = globals
 | 
						|
 | 
						|
# Don't change the indentation of the template; the reindent() calls
 | 
						|
# in Timer.__init__() depend on setup being indented 4 spaces and stmt
 | 
						|
# being indented 8 spaces.
 | 
						|
template = """
 | 
						|
def inner(_it, _timer{init}):
 | 
						|
    {setup}
 | 
						|
    _t0 = _timer()
 | 
						|
    for _i in _it:
 | 
						|
        {stmt}
 | 
						|
    _t1 = _timer()
 | 
						|
    return _t1 - _t0
 | 
						|
"""
 | 
						|
 | 
						|
def reindent(src, indent):
 | 
						|
    """Helper to reindent a multi-line statement."""
 | 
						|
    return src.replace("\n", "\n" + " "*indent)
 | 
						|
 | 
						|
class Timer:
 | 
						|
    """Class for timing execution speed of small code snippets.
 | 
						|
 | 
						|
    The constructor takes a statement to be timed, an additional
 | 
						|
    statement used for setup, and a timer function.  Both statements
 | 
						|
    default to 'pass'; the timer function is platform-dependent (see
 | 
						|
    module doc string).  If 'globals' is specified, the code will be
 | 
						|
    executed within that namespace (as opposed to inside timeit's
 | 
						|
    namespace).
 | 
						|
 | 
						|
    To measure the execution time of the first statement, use the
 | 
						|
    timeit() method.  The repeat() method is a convenience to call
 | 
						|
    timeit() multiple times and return a list of results.
 | 
						|
 | 
						|
    The statements may contain newlines, as long as they don't contain
 | 
						|
    multi-line string literals.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, stmt="pass", setup="pass", timer=default_timer,
 | 
						|
                 globals=None):
 | 
						|
        """Constructor.  See class doc string."""
 | 
						|
        self.timer = timer
 | 
						|
        local_ns = {}
 | 
						|
        global_ns = _globals() if globals is None else globals
 | 
						|
        init = ''
 | 
						|
        if isinstance(setup, str):
 | 
						|
            # Check that the code can be compiled outside a function
 | 
						|
            compile(setup, dummy_src_name, "exec")
 | 
						|
            setup = reindent(setup, 4)
 | 
						|
        elif callable(setup):
 | 
						|
            local_ns['_setup'] = setup
 | 
						|
            init += ', _setup=_setup'
 | 
						|
            setup = '_setup()'
 | 
						|
        else:
 | 
						|
            raise ValueError("setup is neither a string nor callable")
 | 
						|
        if isinstance(stmt, str):
 | 
						|
            # Check that the code can be compiled outside a function
 | 
						|
            if isinstance(setup, str):
 | 
						|
                compile(setup + '\n' + stmt, dummy_src_name, "exec")
 | 
						|
            else:
 | 
						|
                compile(stmt, dummy_src_name, "exec")
 | 
						|
            stmt = reindent(stmt, 8)
 | 
						|
        elif callable(stmt):
 | 
						|
            local_ns['_stmt'] = stmt
 | 
						|
            init += ', _stmt=_stmt'
 | 
						|
            stmt = '_stmt()'
 | 
						|
        else:
 | 
						|
            raise ValueError("stmt is neither a string nor callable")
 | 
						|
        src = template.format(stmt=stmt, setup=setup, init=init)
 | 
						|
        self.src = src  # Save for traceback display
 | 
						|
        code = compile(src, dummy_src_name, "exec")
 | 
						|
        exec(code, global_ns, local_ns)
 | 
						|
        self.inner = local_ns["inner"]
 | 
						|
 | 
						|
    def print_exc(self, file=None):
 | 
						|
        """Helper to print a traceback from the timed code.
 | 
						|
 | 
						|
        Typical use:
 | 
						|
 | 
						|
            t = Timer(...)       # outside the try/except
 | 
						|
            try:
 | 
						|
                t.timeit(...)    # or t.repeat(...)
 | 
						|
            except:
 | 
						|
                t.print_exc()
 | 
						|
 | 
						|
        The advantage over the standard traceback is that source lines
 | 
						|
        in the compiled template will be displayed.
 | 
						|
 | 
						|
        The optional file argument directs where the traceback is
 | 
						|
        sent; it defaults to sys.stderr.
 | 
						|
        """
 | 
						|
        import linecache, traceback
 | 
						|
        if self.src is not None:
 | 
						|
            linecache.cache[dummy_src_name] = (len(self.src),
 | 
						|
                                               None,
 | 
						|
                                               self.src.split("\n"),
 | 
						|
                                               dummy_src_name)
 | 
						|
        # else the source is already stored somewhere else
 | 
						|
 | 
						|
        traceback.print_exc(file=file)
 | 
						|
 | 
						|
    def timeit(self, number=default_number):
 | 
						|
        """Time 'number' executions of the main statement.
 | 
						|
 | 
						|
        To be precise, this executes the setup statement once, and
 | 
						|
        then returns the time it takes to execute the main statement
 | 
						|
        a number of times, as a float measured in seconds.  The
 | 
						|
        argument is the number of times through the loop, defaulting
 | 
						|
        to one million.  The main statement, the setup statement and
 | 
						|
        the timer function to be used are passed to the constructor.
 | 
						|
        """
 | 
						|
        it = itertools.repeat(None, number)
 | 
						|
        gcold = gc.isenabled()
 | 
						|
        gc.disable()
 | 
						|
        try:
 | 
						|
            timing = self.inner(it, self.timer)
 | 
						|
        finally:
 | 
						|
            if gcold:
 | 
						|
                gc.enable()
 | 
						|
        return timing
 | 
						|
 | 
						|
    def repeat(self, repeat=default_repeat, number=default_number):
 | 
						|
        """Call timeit() a few times.
 | 
						|
 | 
						|
        This is a convenience function that calls the timeit()
 | 
						|
        repeatedly, returning a list of results.  The first argument
 | 
						|
        specifies how many times to call timeit(), defaulting to 3;
 | 
						|
        the second argument specifies the timer argument, defaulting
 | 
						|
        to one million.
 | 
						|
 | 
						|
        Note: it's tempting to calculate mean and standard deviation
 | 
						|
        from the result vector and report these.  However, this is not
 | 
						|
        very useful.  In a typical case, the lowest value gives a
 | 
						|
        lower bound for how fast your machine can run the given code
 | 
						|
        snippet; higher values in the result vector are typically not
 | 
						|
        caused by variability in Python's speed, but by other
 | 
						|
        processes interfering with your timing accuracy.  So the min()
 | 
						|
        of the result is probably the only number you should be
 | 
						|
        interested in.  After that, you should look at the entire
 | 
						|
        vector and apply common sense rather than statistics.
 | 
						|
        """
 | 
						|
        r = []
 | 
						|
        for i in range(repeat):
 | 
						|
            t = self.timeit(number)
 | 
						|
            r.append(t)
 | 
						|
        return r
 | 
						|
 | 
						|
def timeit(stmt="pass", setup="pass", timer=default_timer,
 | 
						|
           number=default_number, globals=None):
 | 
						|
    """Convenience function to create Timer object and call timeit method."""
 | 
						|
    return Timer(stmt, setup, timer, globals).timeit(number)
 | 
						|
 | 
						|
def repeat(stmt="pass", setup="pass", timer=default_timer,
 | 
						|
           repeat=default_repeat, number=default_number, globals=None):
 | 
						|
    """Convenience function to create Timer object and call repeat method."""
 | 
						|
    return Timer(stmt, setup, timer, globals).repeat(repeat, number)
 | 
						|
 | 
						|
def main(args=None, *, _wrap_timer=None):
 | 
						|
    """Main program, used when run as a script.
 | 
						|
 | 
						|
    The optional 'args' argument specifies the command line to be parsed,
 | 
						|
    defaulting to sys.argv[1:].
 | 
						|
 | 
						|
    The return value is an exit code to be passed to sys.exit(); it
 | 
						|
    may be None to indicate success.
 | 
						|
 | 
						|
    When an exception happens during timing, a traceback is printed to
 | 
						|
    stderr and the return value is 1.  Exceptions at other times
 | 
						|
    (including the template compilation) are not caught.
 | 
						|
 | 
						|
    '_wrap_timer' is an internal interface used for unit testing.  If it
 | 
						|
    is not None, it must be a callable that accepts a timer function
 | 
						|
    and returns another timer function (used for unit testing).
 | 
						|
    """
 | 
						|
    if args is None:
 | 
						|
        args = sys.argv[1:]
 | 
						|
    import getopt
 | 
						|
    try:
 | 
						|
        opts, args = getopt.getopt(args, "n:u:s:r:tcpvh",
 | 
						|
                                   ["number=", "setup=", "repeat=",
 | 
						|
                                    "time", "clock", "process",
 | 
						|
                                    "verbose", "unit=", "help"])
 | 
						|
    except getopt.error as err:
 | 
						|
        print(err)
 | 
						|
        print("use -h/--help for command line help")
 | 
						|
        return 2
 | 
						|
    timer = default_timer
 | 
						|
    stmt = "\n".join(args) or "pass"
 | 
						|
    number = 0 # auto-determine
 | 
						|
    setup = []
 | 
						|
    repeat = default_repeat
 | 
						|
    verbose = 0
 | 
						|
    time_unit = None
 | 
						|
    units = {"usec": 1, "msec": 1e3, "sec": 1e6}
 | 
						|
    precision = 3
 | 
						|
    for o, a in opts:
 | 
						|
        if o in ("-n", "--number"):
 | 
						|
            number = int(a)
 | 
						|
        if o in ("-s", "--setup"):
 | 
						|
            setup.append(a)
 | 
						|
        if o in ("-u", "--unit"):
 | 
						|
            if a in units:
 | 
						|
                time_unit = a
 | 
						|
            else:
 | 
						|
                print("Unrecognized unit. Please select usec, msec, or sec.",
 | 
						|
                    file=sys.stderr)
 | 
						|
                return 2
 | 
						|
        if o in ("-r", "--repeat"):
 | 
						|
            repeat = int(a)
 | 
						|
            if repeat <= 0:
 | 
						|
                repeat = 1
 | 
						|
        if o in ("-t", "--time"):
 | 
						|
            timer = time.time
 | 
						|
        if o in ("-c", "--clock"):
 | 
						|
            timer = time.clock
 | 
						|
        if o in ("-p", "--process"):
 | 
						|
            timer = time.process_time
 | 
						|
        if o in ("-v", "--verbose"):
 | 
						|
            if verbose:
 | 
						|
                precision += 1
 | 
						|
            verbose += 1
 | 
						|
        if o in ("-h", "--help"):
 | 
						|
            print(__doc__, end=' ')
 | 
						|
            return 0
 | 
						|
    setup = "\n".join(setup) or "pass"
 | 
						|
    # Include the current directory, so that local imports work (sys.path
 | 
						|
    # contains the directory of this script, rather than the current
 | 
						|
    # directory)
 | 
						|
    import os
 | 
						|
    sys.path.insert(0, os.curdir)
 | 
						|
    if _wrap_timer is not None:
 | 
						|
        timer = _wrap_timer(timer)
 | 
						|
    t = Timer(stmt, setup, timer)
 | 
						|
    if number == 0:
 | 
						|
        # determine number so that 0.2 <= total time < 2.0
 | 
						|
        for i in range(1, 10):
 | 
						|
            number = 10**i
 | 
						|
            try:
 | 
						|
                x = t.timeit(number)
 | 
						|
            except:
 | 
						|
                t.print_exc()
 | 
						|
                return 1
 | 
						|
            if verbose:
 | 
						|
                print("%d loops -> %.*g secs" % (number, precision, x))
 | 
						|
            if x >= 0.2:
 | 
						|
                break
 | 
						|
    try:
 | 
						|
        r = t.repeat(repeat, number)
 | 
						|
    except:
 | 
						|
        t.print_exc()
 | 
						|
        return 1
 | 
						|
    best = min(r)
 | 
						|
    if verbose:
 | 
						|
        print("raw times:", " ".join(["%.*g" % (precision, x) for x in r]))
 | 
						|
    print("%d loops," % number, end=' ')
 | 
						|
    usec = best * 1e6 / number
 | 
						|
    if time_unit is not None:
 | 
						|
        print("best of %d: %.*g %s per loop" % (repeat, precision,
 | 
						|
                                             usec/units[time_unit], time_unit))
 | 
						|
    else:
 | 
						|
        if usec < 1000:
 | 
						|
            print("best of %d: %.*g usec per loop" % (repeat, precision, usec))
 | 
						|
        else:
 | 
						|
            msec = usec / 1000
 | 
						|
            if msec < 1000:
 | 
						|
                print("best of %d: %.*g msec per loop" % (repeat,
 | 
						|
                                                          precision, msec))
 | 
						|
            else:
 | 
						|
                sec = msec / 1000
 | 
						|
                print("best of %d: %.*g sec per loop" % (repeat,
 | 
						|
                                                         precision, sec))
 | 
						|
    return None
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    sys.exit(main())
 |