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.
This commit is contained in:
Victor Stinner 2016-10-18 17:06:56 +02:00
parent bf9655854b
commit f8fb82cd25
3 changed files with 32 additions and 18 deletions

View File

@ -226,7 +226,7 @@ class TestTimeit(unittest.TestCase):
t.print_exc(s) t.print_exc(s)
self.assert_exc_string(s.getvalue(), 'ZeroDivisionError') 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): def run_main(self, seconds_per_increment=1.0, switches=None, timer=None):
if timer is None: if timer is None:
@ -252,7 +252,7 @@ class TestTimeit(unittest.TestCase):
def test_main_seconds(self): def test_main_seconds(self):
s = self.run_main(seconds_per_increment=5.5) 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): def test_main_milliseconds(self):
s = self.run_main(seconds_per_increment=0.0055) s = self.run_main(seconds_per_increment=0.0055)
@ -280,11 +280,11 @@ class TestTimeit(unittest.TestCase):
def test_main_fixed_reps(self): def test_main_fixed_reps(self):
s = self.run_main(seconds_per_increment=60.0, switches=['-r9']) 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): def test_main_negative_reps(self):
s = self.run_main(seconds_per_increment=60.0, switches=['-r-5']) 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__") @unittest.skipIf(sys.flags.optimize >= 2, "need __doc__")
def test_main_help(self): def test_main_help(self):
@ -308,14 +308,15 @@ class TestTimeit(unittest.TestCase):
def test_main_verbose(self): def test_main_verbose(self):
s = self.run_main(switches=['-v']) s = self.run_main(switches=['-v'])
self.assertEqual(s, dedent("""\ self.assertEqual(s, dedent("""\
10 loops -> 10 secs 1 loop -> 1 secs
raw times: 10 10 10 raw times: 1 1 1
10 loops, best of 3: 1 sec per loop 1 loop, best of 3: 1 sec per loop
""")) """))
def test_main_very_verbose(self): def test_main_very_verbose(self):
s = self.run_main(seconds_per_increment=0.000050, switches=['-vv']) s = self.run_main(seconds_per_increment=0.000050, switches=['-vv'])
self.assertEqual(s, dedent("""\ self.assertEqual(s, dedent("""\
1 loop -> 5e-05 secs
10 loops -> 0.0005 secs 10 loops -> 0.0005 secs
100 loops -> 0.005 secs 100 loops -> 0.005 secs
1000 loops -> 0.05 secs 1000 loops -> 0.05 secs
@ -328,15 +329,15 @@ class TestTimeit(unittest.TestCase):
unit_sec = self.run_main(seconds_per_increment=0.002, unit_sec = self.run_main(seconds_per_increment=0.002,
switches=['-u', 'sec']) switches=['-u', 'sec'])
self.assertEqual(unit_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, unit_msec = self.run_main(seconds_per_increment=0.002,
switches=['-u', 'msec']) switches=['-u', 'msec'])
self.assertEqual(unit_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, unit_usec = self.run_main(seconds_per_increment=0.002,
switches=['-u', 'usec']) switches=['-u', 'usec'])
self.assertEqual(unit_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 # Test invalid unit input
with captured_stderr() as error_stringio: with captured_stderr() as error_stringio:
invalid = self.run_main(seconds_per_increment=0.002, 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']) s = self.run_main(switches=['-n1', '1/0'])
self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError') self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError')
def autorange(self, callback=None): def autorange(self, seconds_per_increment=0.001, callback=None):
timer = FakeTimer(seconds_per_increment=0.001) timer = FakeTimer(seconds_per_increment=seconds_per_increment)
t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer) t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer)
return t.autorange(callback) return t.autorange(callback)
@ -364,14 +365,20 @@ class TestTimeit(unittest.TestCase):
self.assertEqual(num_loops, 1000) self.assertEqual(num_loops, 1000)
self.assertEqual(time_taken, 1.0) 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 test_autorange_with_callback(self):
def callback(a, b): def callback(a, b):
print("{} {:.3f}".format(a, b)) print("{} {:.3f}".format(a, b))
with captured_stdout() as s: 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(num_loops, 1000)
self.assertEqual(time_taken, 1.0) self.assertEqual(time_taken, 1.0)
expected = ('10 0.010\n' expected = ('1 0.001\n'
'10 0.010\n'
'100 0.100\n' '100 0.100\n'
'1000 1.000\n') '1000 1.000\n')
self.assertEqual(s.getvalue(), expected) self.assertEqual(s.getvalue(), expected)

View File

@ -218,7 +218,7 @@ class Timer:
If *callback* is given and is not None, it will be called after If *callback* is given and is not None, it will be called after
each trial with two arguments: ``callback(number, time_taken)``. each trial with two arguments: ``callback(number, time_taken)``.
""" """
for i in range(1, 10): for i in range(0, 10):
number = 10**i number = 10**i
time_taken = self.timeit(number) time_taken = self.timeit(number)
if callback: if callback:
@ -318,8 +318,10 @@ def main(args=None, *, _wrap_timer=None):
callback = None callback = None
if verbose: if verbose:
def callback(number, time_taken): def callback(number, time_taken):
msg = "{num} loops -> {secs:.{prec}g} secs" msg = "{num} loop{s} -> {secs:.{prec}g} secs"
print(msg.format(num=number, secs=time_taken, prec=precision)) plural = (number != 1)
print(msg.format(num=number, s='s' if plural else '',
secs=time_taken, prec=precision))
try: try:
number, _ = t.autorange(callback) number, _ = t.autorange(callback)
except: except:
@ -333,7 +335,7 @@ def main(args=None, *, _wrap_timer=None):
best = min(r) best = min(r)
if verbose: if verbose:
print("raw times:", " ".join(["%.*g" % (precision, x) for x in r])) 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 usec = best * 1e6 / number
if time_unit is not None: if time_unit is not None:
scale = units[time_unit] scale = units[time_unit]

View File

@ -88,6 +88,11 @@ Core and Builtins
Library 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 - Distutils.sdist now looks for README and setup.py files with case
sensitivity. This behavior matches that found in Setuptools 6.0 and sensitivity. This behavior matches that found in Setuptools 6.0 and
later. See `setuptools 100 later. See `setuptools 100