diff --git a/Doc/library/timeit.rst b/Doc/library/timeit.rst --- a/Doc/library/timeit.rst +++ b/Doc/library/timeit.rst @@ -14,11 +14,64 @@ -------------- This module provides a simple way to time small bits of Python code. It has both -command line as well as callable interfaces. It 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. +a :ref:`command-line-interface` as well as :ref:`callable ` +one. It 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. -The module defines the following public class: + +Basic Examples +-------------- + +The following example shows how the :ref:`command-line-interface`, +can be used to compare three different expressions: + +.. code-block:: sh + + $ python -m timeit '"-".join(str(n) for n in range(100))' + 10000 loops, best of 3: 40.3 usec per loop + $ python -m timeit '"-".join([str(n) for n in range(100)])' + 10000 loops, best of 3: 33.4 usec per loop + $ python -m timeit '"-".join(map(str, range(100)))' + 10000 loops, best of 3: 25.2 usec per loop + +The equivalent can be achieved from the :ref:`python-interface` with:: + + >>> import timeit + >>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000) + 0.8187260627746582 + >>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000) + 0.7288308143615723 + >>> timeit.timeit('"-".join(map(str, range(100)))', number=10000) + 0.5858950614929199 + +In the :ref:`timeit-examples` section you can find more advanced examples. + + +.. _python-interface: + +Python interface +---------------- + +The module defines three convenience functions and a public class: + + +.. function:: timeit(stmt='pass', setup='pass', timer=, number=1000000) + + Create a :class:`Timer` instance with the given statement, setup code and timer + function and run its :meth:`.timeit` method with *number* executions. + + +.. function:: repeat(stmt='pass', setup='pass', timer=, repeat=3, number=1000000) + + Create a :class:`Timer` instance with the given statement, setup code and timer + function and run its :meth:`.repeat` method with the given repeat count and + *number* executions. + + +.. function:: default_timer() + + The default timer, which is always :func:`time.perf_counter`. .. class:: Timer(stmt='pass', setup='pass', timer=) @@ -31,16 +84,57 @@ may also contain multiple statements separated by ``;`` or newlines, as long as they don't contain multi-line string literals. - To measure the execution time of the first statement, use the :meth:`timeit` - method. The :meth:`repeat` method is a convenience to call :meth:`timeit` + To measure the execution time of the first statement, use the :meth:`.timeit` + method. The :meth:`.repeat` method is a convenience to call :meth:`.timeit` multiple times and return a list of results. The *stmt* and *setup* parameters can also take objects that are callable - without arguments. This will embed calls to them in a timer function that - will then be executed by :meth:`timeit`. Note that the timing overhead is a + without arguments. This will embed calls to them in a timer function that + will then be executed by :meth:`.timeit`. Note that the timing overhead is a little larger in this case because of the extra function calls. +.. method:: Timer.timeit(number=1000000) + + Time *number* executions of the main statement. This executes the setup + statement once, and then returns the time it takes to execute the main statement + a number of times, measured in seconds as a float. 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. + + .. note:: + + By default, :meth:`.timeit` temporarily turns off :term:`garbage collection` + during the timing. The advantage of this approach is that it makes + independent timings more comparable. This disadvantage is that GC may be + an important component of the performance of the function being measured. + If so, GC can be re-enabled as the first statement in the *setup* string. + For example:: + + timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit() + + +.. method:: Timer.repeat(repeat=3, number=1000000) + + Call :meth:`.timeit` a few times. + + This is a convenience function that calls the :meth:`.timeit` repeatedly, + returning a list of results. The first argument specifies how many times to + call :meth:`.timeit`. The second argument specifies the *number* argument for + :meth:`.timeit`. + + .. 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 :func:`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. + + .. method:: Timer.print_exc(file=None) Helper to print a traceback from the timed code. @@ -54,73 +148,13 @@ 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``. + template will be displayed. The optional *file* argument directs where the + traceback is sent; it defaults to :data:`sys.stderr`. -.. method:: Timer.repeat(repeat=3, number=1000000) +.. _command-line-interface: - Call :meth:`timeit` a few times. - - This is a convenience function that calls the :meth:`timeit` repeatedly, - returning a list of results. The first argument specifies how many times to - call :meth:`timeit`. The second argument specifies the *number* argument for - :func:`timeit`. - - .. 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 :func:`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. - - -.. method:: Timer.timeit(number=1000000) - - Time *number* executions of the main statement. This executes the setup - statement once, and then returns the time it takes to execute the main statement - a number of times, measured in seconds as a float. 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. - - .. note:: - - By default, :meth:`timeit` temporarily turns off :term:`garbage collection` - during the timing. The advantage of this approach is that it makes - independent timings more comparable. This disadvantage is that GC may be - an important component of the performance of the function being measured. - If so, GC can be re-enabled as the first statement in the *setup* string. - For example:: - - timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit() - - -The module also defines three convenience functions: - - -.. function:: default_timer() - - The default timer, which is always :func:`time.perf_counter`. - - -.. function:: repeat(stmt='pass', setup='pass', timer=, repeat=3, number=1000000) - - Create a :class:`Timer` instance with the given statement, setup code and timer - function and run its :meth:`repeat` method with the given repeat count and - *number* executions. - - -.. function:: timeit(stmt='pass', setup='pass', timer=, number=1000000) - - Create a :class:`Timer` instance with the given statement, setup code and timer - function and run its :meth:`timeit` method with *number* executions. - - -Command Line Interface +Command-Line Interface ---------------------- When called as a program from the command line, the following form is used:: @@ -184,25 +218,53 @@ There is a certain baseline overhead associated with executing a pass statement. 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. + baseline overhead can be measured by invoking the program without arguments and + it might differ between Python versions. -The baseline overhead differs between Python versions! Also, to fairly compare -older Python versions to Python 2.3, you may want to use Python's :option:`-O` -option for the older versions to avoid timing ``SET_LINENO`` instructions. +.. _timeit-examples: Examples -------- -Here are two example sessions (one using the command line, one using the module -interface) that compare the cost of using :func:`hasattr` vs. -:keyword:`try`/:keyword:`except` to test for missing and present object -attributes. :: +It is possible to provide a setup statement that is executed only once at the beginning: + +.. code-block:: sh + + $ python -m timeit -s 'text = "sample string"; char = "g"' 'char in text' + 10000000 loops, best of 3: 0.0877 usec per loop + $ python -m timeit -s 'text = "sample string"; char = "g"' 'text.find(char)' + 1000000 loops, best of 3: 0.342 usec per loop + +:: + + >>> import timeit + >>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"') + 0.41440500499993504 + >>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"') + 1.7246671520006203 + +The same can be done using the :class:`Timer` class and its methods:: + + >>> import timeit + >>> t = Timer('char in text', setup='text = "sample string"; char = "g"') + >>> t.timeit() + 0.3955516149999312 + >>> t.repeat() + [0.40193588800002544, 0.3960157959998014, 0.39594301399984033] + + +The following examples show how to time expressions that contain multiple lines. +Here we compare the cost of using :func:`hasattr` vs. :keyword:`try`/:keyword:`except` +to test for missing and present object attributes: + +.. code-block:: sh $ python -m timeit 'try:' ' str.__bool__' 'except AttributeError:' ' pass' 100000 loops, best of 3: 15.7 usec per loop $ python -m timeit 'if hasattr(str, "__bool__"): pass' 100000 loops, best of 3: 4.26 usec per loop + $ python -m timeit 'try:' ' int.__bool__' 'except AttributeError:' ' pass' 1000000 loops, best of 3: 1.43 usec per loop $ python -m timeit 'if hasattr(int, "__bool__"): pass' @@ -211,46 +273,41 @@ :: >>> import timeit + >>> # attribute is missing >>> s = """\ ... try: ... str.__bool__ ... except AttributeError: ... pass ... """ - >>> t = timeit.Timer(stmt=s) - >>> print("%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)) - 17.09 usec/pass - >>> s = """\ - ... if hasattr(str, '__bool__'): pass - ... """ - >>> t = timeit.Timer(stmt=s) - >>> print("%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)) - 4.85 usec/pass + >>> timeit.timeit(stmt=s, number=100000) + 0.9138244460009446 + >>> s = "if hasattr(str, '__bool__'): pass" + >>> timeit.timeit(stmt=s, number=100000) + 0.5829014980008651 + >>> + >>> # attribute is present >>> s = """\ ... try: ... int.__bool__ ... except AttributeError: ... pass ... """ - >>> t = timeit.Timer(stmt=s) - >>> print("%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)) - 1.97 usec/pass - >>> s = """\ - ... if hasattr(int, '__bool__'): pass - ... """ - >>> t = timeit.Timer(stmt=s) - >>> print("%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)) - 3.15 usec/pass + >>> timeit.timeit(stmt=s, number=100000) + 0.04215312199994514 + >>> s = "if hasattr(int, '__bool__'): pass" + >>> timeit.timeit(stmt=s, number=100000) + 0.08588060699912603 + To give the :mod:`timeit` module access to functions you define, you can pass a -``setup`` parameter which contains an import statement:: +*setup* argument which contains an import statement:: def test(): """Stupid test function""" L = [i for i in range(100)] if __name__ == '__main__': - from timeit import Timer - t = Timer("test()", "from __main__ import test") - print(t.timeit()) + import timeit + print(timeit.timeit("test()", setup="from __main__ import test"))