From 09f4f711b67a310873a4cb084f7c413ef3addcd3 Mon Sep 17 00:00:00 2001 From: Steven D'Aprano Date: Mon, 15 Aug 2016 01:27:03 +1000 Subject: [PATCH] Issue6422 add autorange method to timeit.Timer --- Doc/library/timeit.rst | 19 +++++++++++++++++-- Lib/test/test_timeit.py | 22 ++++++++++++++++++++++ Lib/timeit.py | 41 ++++++++++++++++++++++++++++++----------- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/Doc/library/timeit.rst b/Doc/library/timeit.rst index 57a4834c90e..f046591a808 100644 --- a/Doc/library/timeit.rst +++ b/Doc/library/timeit.rst @@ -100,8 +100,8 @@ The module defines three convenience functions and a public class: can be controlled by passing a namespace to *globals*. 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. + method. The :meth:`.repeat` and :meth:`.autorange` methods are convenience + methods to call :meth:`.timeit` multiple times. The execution time of *setup* is excluded from the overall timed execution run. @@ -134,6 +134,21 @@ 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) + + 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 + (number of loops, time taken for that number of loops). It calls + :meth:`.timeit` with *number* set to successive powers of ten (10, + 100, 1000, ...) up to a maximum of one billion, until the time taken + is at least 0.2 second, or the maximum is reached. + + If *callback* is given and is not *None*, it will be called after + each trial with two arguments: ``callback(number, time_taken)``. + + .. method:: Timer.repeat(repeat=3, number=1000000) Call :meth:`.timeit` a few times. diff --git a/Lib/test/test_timeit.py b/Lib/test/test_timeit.py index 2db3c1bed2a..1a95e2979cd 100644 --- a/Lib/test/test_timeit.py +++ b/Lib/test/test_timeit.py @@ -354,6 +354,28 @@ class TestTimeit(unittest.TestCase): s = self.run_main(switches=['-n1', '1/0']) self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError') + def autorange(self, callback=None): + timer = FakeTimer(seconds_per_increment=0.001) + t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer) + return t.autorange(callback) + + def test_autorange(self): + num_loops, time_taken = self.autorange() + self.assertEqual(num_loops, 1000) + 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) + self.assertEqual(num_loops, 1000) + self.assertEqual(time_taken, 1.0) + expected = ('10 0.010\n' + '100 0.100\n' + '1000 1.000\n') + self.assertEqual(s.getvalue(), expected) + if __name__ == '__main__': unittest.main() diff --git a/Lib/timeit.py b/Lib/timeit.py index 98cb3eb89a8..2770efa35a0 100644 --- a/Lib/timeit.py +++ b/Lib/timeit.py @@ -207,6 +207,26 @@ class Timer: r.append(t) return r + def autorange(self, callback=None): + """Return the number of loops so that total time >= 0.2. + + Calls the timeit method with *number* set to successive powers of + ten (10, 100, 1000, ...) up to a maximum of one billion, until + the time taken is at least 0.2 second, or the maximum 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)``. + """ + for i in range(1, 10): + number = 10**i + time_taken = self.timeit(number) + if callback: + callback(number, time_taken) + if time_taken >= 0.2: + break + return (number, time_taken) + def timeit(stmt="pass", setup="pass", timer=default_timer, number=default_number, globals=None): """Convenience function to create Timer object and call timeit method.""" @@ -295,17 +315,16 @@ def main(args=None, *, _wrap_timer=None): 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 + callback = None + if verbose: + def callback(number, time_taken): + msg = "{num} loops -> {secs:.{prec}g} secs" + print(msg.format(num=number, secs=time_taken, prec=precision)) + try: + number, _ = t.autorange(callback) + except: + t.print_exc() + return 1 try: r = t.repeat(repeat, number) except: