Skip to content
Closed
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
12 changes: 8 additions & 4 deletions Doc/library/timeit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ The module defines three convenience functions and a public class:
: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 @@ -119,6 +119,8 @@ The module defines three convenience functions and a public class:

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

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

Expand All @@ -141,20 +143,22 @@ The module defines three convenience functions and a public class:
timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()


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

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 is greater than or equal to *target_time*, 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 second.
10, 20, 50, ... until the time taken is at least *target_time*.

If *callback* is given and is not ``None``, it will be called after
each trial with two arguments: ``callback(number, time_taken)``.

.. versionadded:: 3.6
.. versionchanged:: 3.8
The optional *target_time* parameter was added.


.. method:: Timer.repeat(repeat=5, number=1000000)
Expand Down
93 changes: 68 additions & 25 deletions Lib/test/test_timeit.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
# timeit's default number of iterations.
DEFAULT_NUMBER = 1000000

# timeit's default target time.
DEFAULT_TARGET_TIME = 0.2

# timeit's default number of repetitions.
DEFAULT_REPEAT = 5

Expand Down Expand Up @@ -107,8 +110,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 if number else 1)
self.assertEqual(delta_time, number if number else (1, 1.0))

# Takes too long to run in debug build.
#def test_timeit_default_iters(self):
Expand Down Expand Up @@ -139,7 +142,7 @@ 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_globals_args(self):
global _global_timer
Expand All @@ -165,9 +168,13 @@ def repeat(self, stmt, setup, repeat=None, number=None):
else:
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.setup_calls, repeat if number > 0
else 1)
# self.assertEqual(self.fake_timer.setup_calls, )
self.assertEqual(self.fake_timer.count,
(repeat * number) if number else 1)
self.assertEqual(delta_times,
repeat * [float(number)] if number else (1, 1.0))

# Takes too long to run in debug build.
#def test_repeat_default(self):
Expand All @@ -179,6 +186,9 @@ def test_repeat_zero_reps(self):
def test_repeat_zero_iters(self):
self.repeat(self.fake_stmt, self.fake_setup, number=0)

def test_repeat_with_iters(self):
self.repeat(self.fake_stmt, self.fake_setup, number=1)

def test_repeat_few_reps_and_iters(self):
self.repeat(self.fake_stmt, self.fake_setup, repeat=3, number=5)

Expand Down Expand Up @@ -208,7 +218,12 @@ 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, (1, 1.0))

def test_repeat_function_with_iters(self):
delta_times = timeit.repeat(self.fake_stmt, self.fake_setup, number=1,
timer=FakeTimer())
self.assertEqual(delta_times, [1.0, 1.0, 1.0, 1.0, 1.0])

def assert_exc_string(self, exc_string, expected_exc_name):
exc_lines = exc_string.splitlines()
Expand Down Expand Up @@ -354,38 +369,66 @@ def test_main_exception_fixed_reps(self):
s = self.run_main(switches=['-n1', '1/0'])
self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError')

def autorange(self, seconds_per_increment=1/1024, callback=None):
def autorange(self, seconds_per_increment=1/1024, callback=None,
target_time=0.2):
timer = FakeTimer(seconds_per_increment=seconds_per_increment)
t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer)
return t.autorange(callback)
return t.autorange(callback, target_time)

def autorange_with_callback(self, loop_count, expected_output,
target_time=0.2):
def callback(a, b):
print("{} {:.3f}".format(a, b))
with captured_stdout() as s:
num_loops, time_taken = self.autorange(callback=callback,
target_time=target_time)
self.assertEqual(num_loops, loop_count)
self.assertEqual(time_taken, loop_count/1024)
self.assertEqual(s.getvalue(), expected_output)

def test_autorange(self):
num_loops, time_taken = self.autorange()
self.assertEqual(num_loops, 500)
self.assertEqual(time_taken, 500/1024)

def test_autorange_with_target_time(self):
num_loops, time_taken = self.autorange(target_time=0.6)
self.assertEqual(num_loops, 1000)
self.assertEqual(time_taken, 1000/1024)

def test_autorange_second(self):
num_loops, time_taken = self.autorange(seconds_per_increment=1.0)
self.assertEqual(num_loops, 1)
self.assertEqual(time_taken, 1.0)

def test_autorange_with_callback(self):
def callback(a, b):
print("{} {:.3f}".format(a, b))
with captured_stdout() as s:
num_loops, time_taken = self.autorange(callback=callback)
self.assertEqual(num_loops, 500)
self.assertEqual(time_taken, 500/1024)
expected = ('1 0.001\n'
'2 0.002\n'
'5 0.005\n'
'10 0.010\n'
'20 0.020\n'
'50 0.049\n'
'100 0.098\n'
'200 0.195\n'
'500 0.488\n')
self.assertEqual(s.getvalue(), expected)
expected = dedent('''\
1 0.001
2 0.002
5 0.005
10 0.010
20 0.020
50 0.049
100 0.098
200 0.195
500 0.488
''')
self.autorange_with_callback(500, expected)

def test_autorange_with_callback_and_target_time(self):
expected = dedent('''\
1 0.001
2 0.002
5 0.005
10 0.010
20 0.020
50 0.049
100 0.098
200 0.195
500 0.488
1000 0.977
''')
self.autorange_with_callback(1000, expected, target_time=0.6)


if __name__ == '__main__':
Expand Down
17 changes: 13 additions & 4 deletions Lib/timeit.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
default_number = 1000000
default_repeat = 5
default_timer = time.perf_counter
default_target_time = 0.2

_globals = globals

Expand Down Expand Up @@ -98,9 +99,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 @@ -169,6 +171,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 number == 0 or number is None:
return self.autorange(target_time=self.target_time)
it = itertools.repeat(None, number)
gcold = gc.isenabled()
gc.disable()
Expand Down Expand Up @@ -199,14 +203,17 @@ def repeat(self, repeat=default_repeat, number=default_number):
interested in. After that, you should look at the entire
vector and apply common sense rather than statistics.
"""
if number == 0 or number is None:
return self.autorange(target_time=self.target_time)
r = []
for i in range(repeat):
t = self.timeit(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
is greater than or equal to *target_time*.

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
Expand All @@ -215,14 +222,16 @@ def autorange(self, callback=None):
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

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Juancarlo Añez
Chris Angelico
Jérémy Anger
Jon Anglin
Michele Angrisano
Ankur Ankan
Heidi Annexstad
Ramchandra Apte
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
A new parameter *total_time* was added to the ``timeit.autorange()``
function.