From f8fb82cd25739fc2fe67c5e6997af28f2a5590d3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 18 Oct 2016 17:06:56 +0200 Subject: timeit: start autorange with 1 iteration, not 10 Issue #28240: timeit autorange now uses a single loop iteration if the benchmark takes less than 10 seconds, instead of 10 iterations. "python3 -m timeit -s 'import time' 'time.sleep(1)'" now takes 4 seconds instead of 40 seconds. --- Lib/test/test_timeit.py | 35 +++++++++++++++++++++-------------- Lib/timeit.py | 10 ++++++---- Misc/NEWS | 5 +++++ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_timeit.py b/Lib/test/test_timeit.py index 1a95e29..3b92992 100644 --- a/Lib/test/test_timeit.py +++ b/Lib/test/test_timeit.py @@ -226,7 +226,7 @@ class TestTimeit(unittest.TestCase): t.print_exc(s) self.assert_exc_string(s.getvalue(), 'ZeroDivisionError') - MAIN_DEFAULT_OUTPUT = "10 loops, best of 3: 1 sec per loop\n" + MAIN_DEFAULT_OUTPUT = "1 loop, best of 3: 1 sec per loop\n" def run_main(self, seconds_per_increment=1.0, switches=None, timer=None): if timer is None: @@ -252,7 +252,7 @@ class TestTimeit(unittest.TestCase): def test_main_seconds(self): s = self.run_main(seconds_per_increment=5.5) - self.assertEqual(s, "10 loops, best of 3: 5.5 sec per loop\n") + self.assertEqual(s, "1 loop, best of 3: 5.5 sec per loop\n") def test_main_milliseconds(self): s = self.run_main(seconds_per_increment=0.0055) @@ -280,11 +280,11 @@ class TestTimeit(unittest.TestCase): def test_main_fixed_reps(self): s = self.run_main(seconds_per_increment=60.0, switches=['-r9']) - self.assertEqual(s, "10 loops, best of 9: 60 sec per loop\n") + self.assertEqual(s, "1 loop, best of 9: 60 sec per loop\n") def test_main_negative_reps(self): s = self.run_main(seconds_per_increment=60.0, switches=['-r-5']) - self.assertEqual(s, "10 loops, best of 1: 60 sec per loop\n") + self.assertEqual(s, "1 loop, best of 1: 60 sec per loop\n") @unittest.skipIf(sys.flags.optimize >= 2, "need __doc__") def test_main_help(self): @@ -308,14 +308,15 @@ class TestTimeit(unittest.TestCase): def test_main_verbose(self): s = self.run_main(switches=['-v']) self.assertEqual(s, dedent("""\ - 10 loops -> 10 secs - raw times: 10 10 10 - 10 loops, best of 3: 1 sec per loop + 1 loop -> 1 secs + raw times: 1 1 1 + 1 loop, best of 3: 1 sec per loop """)) def test_main_very_verbose(self): s = self.run_main(seconds_per_increment=0.000050, switches=['-vv']) self.assertEqual(s, dedent("""\ + 1 loop -> 5e-05 secs 10 loops -> 0.0005 secs 100 loops -> 0.005 secs 1000 loops -> 0.05 secs @@ -328,15 +329,15 @@ class TestTimeit(unittest.TestCase): unit_sec = self.run_main(seconds_per_increment=0.002, switches=['-u', 'sec']) self.assertEqual(unit_sec, - "1000 loops, best of 3: 0.002 sec per loop\n") + "100 loops, best of 3: 0.002 sec per loop\n") unit_msec = self.run_main(seconds_per_increment=0.002, switches=['-u', 'msec']) self.assertEqual(unit_msec, - "1000 loops, best of 3: 2 msec per loop\n") + "100 loops, best of 3: 2 msec per loop\n") unit_usec = self.run_main(seconds_per_increment=0.002, switches=['-u', 'usec']) self.assertEqual(unit_usec, - "1000 loops, best of 3: 2e+03 usec per loop\n") + "100 loops, best of 3: 2e+03 usec per loop\n") # Test invalid unit input with captured_stderr() as error_stringio: invalid = self.run_main(seconds_per_increment=0.002, @@ -354,8 +355,8 @@ 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) + def autorange(self, seconds_per_increment=0.001, callback=None): + 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) @@ -364,14 +365,20 @@ class TestTimeit(unittest.TestCase): self.assertEqual(num_loops, 1000) self.assertEqual(time_taken, 1.0) + 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) + num_loops, time_taken = self.autorange(callback=callback) self.assertEqual(num_loops, 1000) self.assertEqual(time_taken, 1.0) - expected = ('10 0.010\n' + expected = ('1 0.001\n' + '10 0.010\n' '100 0.100\n' '1000 1.000\n') self.assertEqual(s.getvalue(), expected) diff --git a/Lib/timeit.py b/Lib/timeit.py index 2770efa..0040efd 100644 --- a/Lib/timeit.py +++ b/Lib/timeit.py @@ -218,7 +218,7 @@ class Timer: 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): + for i in range(0, 10): number = 10**i time_taken = self.timeit(number) if callback: @@ -318,8 +318,10 @@ def main(args=None, *, _wrap_timer=None): 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)) + msg = "{num} loop{s} -> {secs:.{prec}g} secs" + plural = (number != 1) + print(msg.format(num=number, s='s' if plural else '', + secs=time_taken, prec=precision)) try: number, _ = t.autorange(callback) except: @@ -333,7 +335,7 @@ def main(args=None, *, _wrap_timer=None): best = min(r) if verbose: print("raw times:", " ".join(["%.*g" % (precision, x) for x in r])) - print("%d loops," % number, end=' ') + print("%d loop%s," % (number, 's' if number != 1 else ''), end=' ') usec = best * 1e6 / number if time_unit is not None: scale = units[time_unit] diff --git a/Misc/NEWS b/Misc/NEWS index f5a36f0..7970c3f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -88,6 +88,11 @@ Core and Builtins Library ------- +- Issue #28240: timeit autorange now uses a single loop iteration if the + benchmark takes less than 10 seconds, instead of 10 iterations. + "python3 -m timeit -s 'import time' 'time.sleep(1)'" now takes 4 seconds + instead of 40 seconds. + - Distutils.sdist now looks for README and setup.py files with case sensitivity. This behavior matches that found in Setuptools 6.0 and later. See `setuptools 100 -- cgit v0.12