Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions Doc/library/timeit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,39 @@
The module defines three convenience functions and a public class:


.. function:: timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)
.. function:: timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None, target_time=0.2)

Create a :class:`Timer` instance with the given statement, *setup* code and
*timer* function and run its :meth:`.timeit` method with *number* executions.
The optional *globals* argument specifies a namespace in which to execute the
code.
code. If *number* is 0, :meth:`.autorange` method is executed, a convenience
function that calls :meth:`.timeit` repeatedly so that the total time >=
*target_time* second.

.. versionchanged:: 3.5
The optional *globals* parameter was added.

.. versionchanged:: next
The optional *target_time* parameter was added.

.. function:: repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

.. function:: repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None, target_time=0.2)

Create a :class:`Timer` instance with the given statement, *setup* code and

Check warning on line 83 in Doc/library/timeit.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:meth reference target not found: autorange [ref.meth]
*timer* function and run its :meth:`.repeat` method with the given *repeat*
count and *number* executions. The optional *globals* argument specifies a
namespace in which to execute the code.
namespace in which to execute the code. If *number* is 0, the :meth:`autorange`
method is executed, and a convenience function calls :meth:`timeit`
repeatedly so that the total time >= *target_time* seconds.

.. versionchanged:: 3.5
The optional *globals* parameter was added.

.. versionchanged:: 3.7
Default value of *repeat* changed from 3 to 5.

.. versionchanged:: next
The optional *target_time* parameter was added.

.. function:: default_timer()

Expand All @@ -96,7 +105,7 @@
:func:`time.perf_counter` is now the default timer.


.. class:: Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)
.. class:: Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None, target_time=0.2)

Class for timing execution speed of small code snippets.

Expand All @@ -122,6 +131,9 @@
.. versionchanged:: 3.5
The optional *globals* parameter was added.

.. versionchanged:: next
The optional *target_time* parameter was added.

.. method:: Timer.timeit(number=1000000)

Time *number* executions of the main statement. This executes the setup
Expand All @@ -143,15 +155,15 @@
timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()


.. method:: Timer.autorange(callback=None)
.. method:: Timer.autorange(callback=None, target_time=None)

Automatically determine how many times to call :meth:`.timeit`.

This is a convenience function that calls :meth:`.timeit` repeatedly
so that the total time >= 0.2 second, returning the eventual
so that the total time >= *Timer.target_time* seconds, returning the eventual
(number of loops, time taken for that number of loops). It calls
:meth:`.timeit` with increasing numbers from the sequence 1, 2, 5,
10, 20, 50, ... until the time taken is at least 0.2 seconds.
10, 20, 50, ... until the time taken is at least *target_time* seconds.

If *callback* is given and is not ``None``, it will be called after
each trial with two arguments: ``callback(number, time_taken)``.
Expand Down Expand Up @@ -239,6 +251,12 @@

.. versionadded:: 3.5

.. option:: -t, --target-time=T

calls :meth:`.timeit` repeatedly so that the total time >= *target_time* seconds

.. versionadded:: next

.. option:: -v, --verbose

print raw timing results; repeat for more digits precision
Expand Down
25 changes: 17 additions & 8 deletions Lib/test/test_timeit.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ def timeit(self, stmt, setup, number=None, globals=None):
kwargs['number'] = number
delta_time = t.timeit(**kwargs)
self.assertEqual(self.fake_timer.setup_calls, 1)
self.assertEqual(self.fake_timer.count, number)
self.assertEqual(delta_time, number)
self.assertEqual(self.fake_timer.count, number or 1)
self.assertEqual(delta_time, number or (1, 1.0))

# Takes too long to run in debug build.
#def test_timeit_default_iters(self):
Expand Down Expand Up @@ -149,7 +149,12 @@ def test_timeit_callable_stmt_and_setup(self):
def test_timeit_function_zero_iters(self):
delta_time = timeit.timeit(self.fake_stmt, self.fake_setup, number=0,
timer=FakeTimer())
self.assertEqual(delta_time, 0)
self.assertEqual(delta_time, (1, 1.0))

def test_timeit_function_target_time(self):
delta_time = timeit.timeit(self.fake_stmt, self.fake_setup, number=0,
timer=FakeTimer(), target_time=1)
self.assertEqual(delta_time, (1, 1.0))

def test_timeit_globals_args(self):
global _global_timer
Expand All @@ -162,9 +167,9 @@ def test_timeit_globals_args(self):
timeit.timeit(stmt='local_timer.inc()', timer=local_timer,
globals=locals(), number=3)

def repeat(self, stmt, setup, repeat=None, number=None):
def repeat(self, stmt, setup, repeat=None, number=None, target_time=0.5):
self.fake_timer = FakeTimer()
t = timeit.Timer(stmt=stmt, setup=setup, timer=self.fake_timer)
t = timeit.Timer(stmt=stmt, setup=setup, timer=self.fake_timer, target_time=target_time)
kwargs = {}
if repeat is None:
repeat = DEFAULT_REPEAT
Expand All @@ -176,8 +181,8 @@ def repeat(self, stmt, setup, repeat=None, number=None):
kwargs['number'] = number
delta_times = t.repeat(**kwargs)
self.assertEqual(self.fake_timer.setup_calls, repeat)
self.assertEqual(self.fake_timer.count, repeat * number)
self.assertEqual(delta_times, repeat * [float(number)])
self.assertEqual(self.fake_timer.count, (repeat * number) if number else repeat)
self.assertEqual(delta_times, repeat * [float(number) or (1, 1.0)])

# Takes too long to run in debug build.
#def test_repeat_default(self):
Expand All @@ -196,6 +201,10 @@ def test_repeat_callable_stmt(self):
self.repeat(self.fake_callable_stmt, self.fake_setup,
repeat=3, number=5)

def test_repeat_callable_target_time(self):
self.repeat(self.fake_callable_stmt, self.fake_setup,
repeat=3, number=5, target_time=1)

def test_repeat_callable_setup(self):
self.repeat(self.fake_stmt, self.fake_callable_setup,
repeat=3, number=5)
Expand All @@ -218,7 +227,7 @@ def test_repeat_function_zero_reps(self):
def test_repeat_function_zero_iters(self):
delta_times = timeit.repeat(self.fake_stmt, self.fake_setup, number=0,
timer=FakeTimer())
self.assertEqual(delta_times, DEFAULT_REPEAT * [0.0])
self.assertEqual(delta_times, DEFAULT_REPEAT * [(1, 1.0)])

def assert_exc_string(self, exc_string, expected_exc_name):
exc_lines = exc_string.splitlines()
Expand Down
48 changes: 32 additions & 16 deletions Lib/timeit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
-p/--process: use time.process_time() (default is time.perf_counter())
-v/--verbose: print raw timing results; repeat for more digits precision
-u/--unit: set the output time unit (nsec, usec, msec, or sec)
-t/--target-time T: if --number is 0 the code will run until it
takes *at least* this many seconds
(default: 0.2)
-h/--help: print this usage message and exit
--: separate options from statement, use when statement starts with -
statement: statement to be timed (default 'pass')
Expand All @@ -28,7 +31,7 @@

If -n is not given, a suitable number of loops is calculated by trying
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
total time is at least 0.2 seconds.
total time is at least --target-time seconds.

Note: there is a certain baseline overhead associated with executing a
pass statement. It differs between versions. The code here doesn't try
Expand Down Expand Up @@ -57,6 +60,7 @@
default_number = 1000000
default_repeat = 5
default_timer = time.perf_counter
default_target_time = 0.2

_globals = globals

Expand Down Expand Up @@ -99,9 +103,10 @@ class Timer:
"""

def __init__(self, stmt="pass", setup="pass", timer=default_timer,
globals=None):
globals=None, target_time=default_target_time):
"""Constructor. See class doc string."""
self.timer = timer
self.target_time = target_time
local_ns = {}
global_ns = _globals() if globals is None else globals
init = ''
Expand Down Expand Up @@ -176,6 +181,8 @@ def timeit(self, number=default_number):
to one million. The main statement, the setup statement and
the timer function to be used are passed to the constructor.
"""
if not number:
return self.autorange()
it = itertools.repeat(None, number)
gcold = gc.isenabled()
gc.disable()
Expand Down Expand Up @@ -212,38 +219,43 @@ def repeat(self, repeat=default_repeat, number=default_number):
r.append(t)
return r

def autorange(self, callback=None):
"""Return the number of loops and time taken so that total time >= 0.2.
def autorange(self, callback=None, target_time=None):
"""Return the number of loops and time taken so that
total time >= target_time (default is 0.2 seconds).

Calls the timeit method with increasing numbers from the sequence
1, 2, 5, 10, 20, 50, ... until the time taken is at least 0.2
second. Returns (number, time_taken).
1, 2, 5, 10, 20, 50, ... until the target_time is reached.
Returns (number, time_taken).

If *callback* is given and is not None, it will be called after
each trial with two arguments: ``callback(number, time_taken)``.
"""
if target_time is None:
target_time = self.target_time
i = 1
while True:
for j in 1, 2, 5:
number = i * j
time_taken = self.timeit(number)
if callback:
callback(number, time_taken)
if time_taken >= 0.2:
if time_taken >= target_time:
return (number, time_taken)
i *= 10


def timeit(stmt="pass", setup="pass", timer=default_timer,
number=default_number, globals=None):
number=default_number, globals=None,
target_time=default_target_time):
"""Convenience function to create Timer object and call timeit method."""
return Timer(stmt, setup, timer, globals).timeit(number)
return Timer(stmt, setup, timer, globals, target_time).timeit(number)


def repeat(stmt="pass", setup="pass", timer=default_timer,
repeat=default_repeat, number=default_number, globals=None):
repeat=default_repeat, number=default_number,
globals=None, target_time=default_target_time):
"""Convenience function to create Timer object and call repeat method."""
return Timer(stmt, setup, timer, globals).repeat(repeat, number)
return Timer(stmt, setup, timer, globals, target_time).repeat(repeat, number)


def main(args=None, *, _wrap_timer=None):
Expand All @@ -270,9 +282,10 @@ def main(args=None, *, _wrap_timer=None):
colorize = _colorize.can_colorize()

try:
opts, args = getopt.getopt(args, "n:u:s:r:pvh",
opts, args = getopt.getopt(args, "n:u:s:r:pt:vh",
["number=", "setup=", "repeat=",
"process", "verbose", "unit=", "help"])
"process", "target-time=",
"verbose", "unit=", "help"])
except getopt.error as err:
print(err)
print("use -h/--help for command line help")
Expand All @@ -281,6 +294,7 @@ def main(args=None, *, _wrap_timer=None):
timer = default_timer
stmt = "\n".join(args) or "pass"
number = 0 # auto-determine
target_time = default_target_time
setup = []
repeat = default_repeat
verbose = 0
Expand All @@ -305,6 +319,8 @@ def main(args=None, *, _wrap_timer=None):
repeat = 1
if o in ("-p", "--process"):
timer = time.process_time
if o in ("-t", "--target-time"):
target_time = float(a)
if o in ("-v", "--verbose"):
if verbose:
precision += 1
Expand All @@ -322,9 +338,9 @@ def main(args=None, *, _wrap_timer=None):
if _wrap_timer is not None:
timer = _wrap_timer(timer)

t = Timer(stmt, setup, timer)
t = Timer(stmt, setup, timer, target_time=target_time)
if number == 0:
# determine number so that 0.2 <= total time < 2.0
# determine number so that total time >= target_time
callback = None
if verbose:
def callback(number, time_taken):
Expand All @@ -333,7 +349,7 @@ def callback(number, time_taken):
print(msg.format(num=number, s='s' if plural else '',
secs=time_taken, prec=precision))
try:
number, _ = t.autorange(callback)
number, _ = t.autorange(callback, target_time)
except:
t.print_exc(colorize=colorize)
return 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:mod:`timeit`:
- added a new parameter *target_time* to :func:`timeit.timeit` and
:func:`timeit.repeat` methods and :class:`timeit.Timer` class;
- had ``timeit`` and ``repeat`` methods (and functions) fall back
on ``autorange`` if the number is set to 0 or None.
Loading