(root)/
Python-3.11.7/
Lib/
timeit.py
       1  #! /usr/bin/env python3
       2  
       3  """Tool for measuring execution time of small code snippets.
       4  
       5  This module avoids a number of common traps for measuring execution
       6  times.  See also Tim Peters' introduction to the Algorithms chapter in
       7  the Python Cookbook, published by O'Reilly.
       8  
       9  Library usage: see the Timer class.
      10  
      11  Command line usage:
      12      python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [--] [statement]
      13  
      14  Options:
      15    -n/--number N: how many times to execute 'statement' (default: see below)
      16    -r/--repeat N: how many times to repeat the timer (default 5)
      17    -s/--setup S: statement to be executed once initially (default 'pass').
      18                  Execution time of this setup statement is NOT timed.
      19    -p/--process: use time.process_time() (default is time.perf_counter())
      20    -v/--verbose: print raw timing results; repeat for more digits precision
      21    -u/--unit: set the output time unit (nsec, usec, msec, or sec)
      22    -h/--help: print this usage message and exit
      23    --: separate options from statement, use when statement starts with -
      24    statement: statement to be timed (default 'pass')
      25  
      26  A multi-line statement may be given by specifying each line as a
      27  separate argument; indented lines are possible by enclosing an
      28  argument in quotes and using leading spaces.  Multiple -s options are
      29  treated similarly.
      30  
      31  If -n is not given, a suitable number of loops is calculated by trying
      32  increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
      33  total time is at least 0.2 seconds.
      34  
      35  Note: there is a certain baseline overhead associated with executing a
      36  pass statement.  It differs between versions.  The code here doesn't try
      37  to hide it, but you should be aware of it.  The baseline overhead can be
      38  measured by invoking the program without arguments.
      39  
      40  Classes:
      41  
      42      Timer
      43  
      44  Functions:
      45  
      46      timeit(string, string) -> float
      47      repeat(string, string) -> list
      48      default_timer() -> float
      49  
      50  """
      51  
      52  import gc
      53  import itertools
      54  import sys
      55  import time
      56  
      57  __all__ = ["Timer", "timeit", "repeat", "default_timer"]
      58  
      59  dummy_src_name = "<timeit-src>"
      60  default_number = 1000000
      61  default_repeat = 5
      62  default_timer = time.perf_counter
      63  
      64  _globals = globals
      65  
      66  # Don't change the indentation of the template; the reindent() calls
      67  # in Timer.__init__() depend on setup being indented 4 spaces and stmt
      68  # being indented 8 spaces.
      69  template = """
      70  def inner(_it, _timer{init}):
      71      {setup}
      72      _t0 = _timer()
      73      for _i in _it:
      74          {stmt}
      75          pass
      76      _t1 = _timer()
      77      return _t1 - _t0
      78  """
      79  
      80  
      81  def reindent(src, indent):
      82      """Helper to reindent a multi-line statement."""
      83      return src.replace("\n", "\n" + " " * indent)
      84  
      85  
      86  class ESC[4;38;5;81mTimer:
      87      """Class for timing execution speed of small code snippets.
      88  
      89      The constructor takes a statement to be timed, an additional
      90      statement used for setup, and a timer function.  Both statements
      91      default to 'pass'; the timer function is platform-dependent (see
      92      module doc string).  If 'globals' is specified, the code will be
      93      executed within that namespace (as opposed to inside timeit's
      94      namespace).
      95  
      96      To measure the execution time of the first statement, use the
      97      timeit() method.  The repeat() method is a convenience to call
      98      timeit() multiple times and return a list of results.
      99  
     100      The statements may contain newlines, as long as they don't contain
     101      multi-line string literals.
     102      """
     103  
     104      def __init__(self, stmt="pass", setup="pass", timer=default_timer,
     105                   globals=None):
     106          """Constructor.  See class doc string."""
     107          self.timer = timer
     108          local_ns = {}
     109          global_ns = _globals() if globals is None else globals
     110          init = ''
     111          if isinstance(setup, str):
     112              # Check that the code can be compiled outside a function
     113              compile(setup, dummy_src_name, "exec")
     114              stmtprefix = setup + '\n'
     115              setup = reindent(setup, 4)
     116          elif callable(setup):
     117              local_ns['_setup'] = setup
     118              init += ', _setup=_setup'
     119              stmtprefix = ''
     120              setup = '_setup()'
     121          else:
     122              raise ValueError("setup is neither a string nor callable")
     123          if isinstance(stmt, str):
     124              # Check that the code can be compiled outside a function
     125              compile(stmtprefix + stmt, dummy_src_name, "exec")
     126              stmt = reindent(stmt, 8)
     127          elif callable(stmt):
     128              local_ns['_stmt'] = stmt
     129              init += ', _stmt=_stmt'
     130              stmt = '_stmt()'
     131          else:
     132              raise ValueError("stmt is neither a string nor callable")
     133          src = template.format(stmt=stmt, setup=setup, init=init)
     134          self.src = src  # Save for traceback display
     135          code = compile(src, dummy_src_name, "exec")
     136          exec(code, global_ns, local_ns)
     137          self.inner = local_ns["inner"]
     138  
     139      def print_exc(self, file=None):
     140          """Helper to print a traceback from the timed code.
     141  
     142          Typical use:
     143  
     144              t = Timer(...)       # outside the try/except
     145              try:
     146                  t.timeit(...)    # or t.repeat(...)
     147              except:
     148                  t.print_exc()
     149  
     150          The advantage over the standard traceback is that source lines
     151          in the compiled template will be displayed.
     152  
     153          The optional file argument directs where the traceback is
     154          sent; it defaults to sys.stderr.
     155          """
     156          import linecache, traceback
     157          if self.src is not None:
     158              linecache.cache[dummy_src_name] = (len(self.src),
     159                                                 None,
     160                                                 self.src.split("\n"),
     161                                                 dummy_src_name)
     162          # else the source is already stored somewhere else
     163  
     164          traceback.print_exc(file=file)
     165  
     166      def timeit(self, number=default_number):
     167          """Time 'number' executions of the main statement.
     168  
     169          To be precise, this executes the setup statement once, and
     170          then returns the time it takes to execute the main statement
     171          a number of times, as float seconds if using the default timer.   The
     172          argument is the number of times through the loop, defaulting
     173          to one million.  The main statement, the setup statement and
     174          the timer function to be used are passed to the constructor.
     175          """
     176          it = itertools.repeat(None, number)
     177          gcold = gc.isenabled()
     178          gc.disable()
     179          try:
     180              timing = self.inner(it, self.timer)
     181          finally:
     182              if gcold:
     183                  gc.enable()
     184          return timing
     185  
     186      def repeat(self, repeat=default_repeat, number=default_number):
     187          """Call timeit() a few times.
     188  
     189          This is a convenience function that calls the timeit()
     190          repeatedly, returning a list of results.  The first argument
     191          specifies how many times to call timeit(), defaulting to 5;
     192          the second argument specifies the timer argument, defaulting
     193          to one million.
     194  
     195          Note: it's tempting to calculate mean and standard deviation
     196          from the result vector and report these.  However, this is not
     197          very useful.  In a typical case, the lowest value gives a
     198          lower bound for how fast your machine can run the given code
     199          snippet; higher values in the result vector are typically not
     200          caused by variability in Python's speed, but by other
     201          processes interfering with your timing accuracy.  So the min()
     202          of the result is probably the only number you should be
     203          interested in.  After that, you should look at the entire
     204          vector and apply common sense rather than statistics.
     205          """
     206          r = []
     207          for i in range(repeat):
     208              t = self.timeit(number)
     209              r.append(t)
     210          return r
     211  
     212      def autorange(self, callback=None):
     213          """Return the number of loops and time taken so that total time >= 0.2.
     214  
     215          Calls the timeit method with increasing numbers from the sequence
     216          1, 2, 5, 10, 20, 50, ... until the time taken is at least 0.2
     217          second.  Returns (number, time_taken).
     218  
     219          If *callback* is given and is not None, it will be called after
     220          each trial with two arguments: ``callback(number, time_taken)``.
     221          """
     222          i = 1
     223          while True:
     224              for j in 1, 2, 5:
     225                  number = i * j
     226                  time_taken = self.timeit(number)
     227                  if callback:
     228                      callback(number, time_taken)
     229                  if time_taken >= 0.2:
     230                      return (number, time_taken)
     231              i *= 10
     232  
     233  
     234  def timeit(stmt="pass", setup="pass", timer=default_timer,
     235             number=default_number, globals=None):
     236      """Convenience function to create Timer object and call timeit method."""
     237      return Timer(stmt, setup, timer, globals).timeit(number)
     238  
     239  
     240  def repeat(stmt="pass", setup="pass", timer=default_timer,
     241             repeat=default_repeat, number=default_number, globals=None):
     242      """Convenience function to create Timer object and call repeat method."""
     243      return Timer(stmt, setup, timer, globals).repeat(repeat, number)
     244  
     245  
     246  def main(args=None, *, _wrap_timer=None):
     247      """Main program, used when run as a script.
     248  
     249      The optional 'args' argument specifies the command line to be parsed,
     250      defaulting to sys.argv[1:].
     251  
     252      The return value is an exit code to be passed to sys.exit(); it
     253      may be None to indicate success.
     254  
     255      When an exception happens during timing, a traceback is printed to
     256      stderr and the return value is 1.  Exceptions at other times
     257      (including the template compilation) are not caught.
     258  
     259      '_wrap_timer' is an internal interface used for unit testing.  If it
     260      is not None, it must be a callable that accepts a timer function
     261      and returns another timer function (used for unit testing).
     262      """
     263      if args is None:
     264          args = sys.argv[1:]
     265      import getopt
     266      try:
     267          opts, args = getopt.getopt(args, "n:u:s:r:tcpvh",
     268                                     ["number=", "setup=", "repeat=",
     269                                      "time", "clock", "process",
     270                                      "verbose", "unit=", "help"])
     271      except getopt.error as err:
     272          print(err)
     273          print("use -h/--help for command line help")
     274          return 2
     275  
     276      timer = default_timer
     277      stmt = "\n".join(args) or "pass"
     278      number = 0  # auto-determine
     279      setup = []
     280      repeat = default_repeat
     281      verbose = 0
     282      time_unit = None
     283      units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
     284      precision = 3
     285      for o, a in opts:
     286          if o in ("-n", "--number"):
     287              number = int(a)
     288          if o in ("-s", "--setup"):
     289              setup.append(a)
     290          if o in ("-u", "--unit"):
     291              if a in units:
     292                  time_unit = a
     293              else:
     294                  print("Unrecognized unit. Please select nsec, usec, msec, or sec.",
     295                        file=sys.stderr)
     296                  return 2
     297          if o in ("-r", "--repeat"):
     298              repeat = int(a)
     299              if repeat <= 0:
     300                  repeat = 1
     301          if o in ("-p", "--process"):
     302              timer = time.process_time
     303          if o in ("-v", "--verbose"):
     304              if verbose:
     305                  precision += 1
     306              verbose += 1
     307          if o in ("-h", "--help"):
     308              print(__doc__, end=' ')
     309              return 0
     310      setup = "\n".join(setup) or "pass"
     311  
     312      # Include the current directory, so that local imports work (sys.path
     313      # contains the directory of this script, rather than the current
     314      # directory)
     315      import os
     316      sys.path.insert(0, os.curdir)
     317      if _wrap_timer is not None:
     318          timer = _wrap_timer(timer)
     319  
     320      t = Timer(stmt, setup, timer)
     321      if number == 0:
     322          # determine number so that 0.2 <= total time < 2.0
     323          callback = None
     324          if verbose:
     325              def callback(number, time_taken):
     326                  msg = "{num} loop{s} -> {secs:.{prec}g} secs"
     327                  plural = (number != 1)
     328                  print(msg.format(num=number, s='s' if plural else '',
     329                                   secs=time_taken, prec=precision))
     330          try:
     331              number, _ = t.autorange(callback)
     332          except:
     333              t.print_exc()
     334              return 1
     335  
     336          if verbose:
     337              print()
     338  
     339      try:
     340          raw_timings = t.repeat(repeat, number)
     341      except:
     342          t.print_exc()
     343          return 1
     344  
     345      def format_time(dt):
     346          unit = time_unit
     347  
     348          if unit is not None:
     349              scale = units[unit]
     350          else:
     351              scales = [(scale, unit) for unit, scale in units.items()]
     352              scales.sort(reverse=True)
     353              for scale, unit in scales:
     354                  if dt >= scale:
     355                      break
     356  
     357          return "%.*g %s" % (precision, dt / scale, unit)
     358  
     359      if verbose:
     360          print("raw times: %s" % ", ".join(map(format_time, raw_timings)))
     361          print()
     362      timings = [dt / number for dt in raw_timings]
     363  
     364      best = min(timings)
     365      print("%d loop%s, best of %d: %s per loop"
     366            % (number, 's' if number != 1 else '',
     367               repeat, format_time(best)))
     368  
     369      best = min(timings)
     370      worst = max(timings)
     371      if worst >= best * 4:
     372          import warnings
     373          warnings.warn_explicit("The test results are likely unreliable. "
     374                                 "The worst time (%s) was more than four times "
     375                                 "slower than the best time (%s)."
     376                                 % (format_time(worst), format_time(best)),
     377                                 UserWarning, '', 0)
     378      return None
     379  
     380  
     381  if __name__ == "__main__":
     382      sys.exit(main())