diff -r 08b3ee523577 Lib/test/test_timeit.py --- a/Lib/test/test_timeit.py Tue Jul 15 13:23:58 2014 +0300 +++ b/Lib/test/test_timeit.py Wed Jul 16 11:49:02 2014 +0300 @@ -109,6 +109,9 @@ def test_timeit_few_iters(self): self.timeit(self.fake_stmt, self.fake_setup, number=3) + def test_timeit_many_iters(self): + self.timeit(self.fake_stmt, self.fake_setup, number=2003) + def test_timeit_callable_stmt(self): self.timeit(self.fake_callable_stmt, self.fake_setup, number=3) diff -r 08b3ee523577 Lib/timeit.py --- a/Lib/timeit.py Tue Jul 15 13:23:58 2014 +0300 +++ b/Lib/timeit.py Wed Jul 16 11:49:02 2014 +0300 @@ -59,20 +59,24 @@ default_number = 1000000 default_repeat = 3 default_timer = time.perf_counter +_unroll_number = 1000 # Don't change the indentation of the template; the reindent() calls # in Timer.__init__() depend on setup being indented 4 spaces and stmt # being indented 8 spaces. -template = """ +_template = """ def inner(_it, _timer): {setup} _t0 = _timer() - for _i in _it: - {stmt} + {loop} + {unrolled} _t1 = _timer() return _t1 - _t0 """ +_loop_template = """for _i in _it: + {loopbody}""" + def reindent(src, indent): """Helper to reindent a multi-line statement.""" return src.replace("\n", "\n" + " "*indent) @@ -109,19 +113,10 @@ self.timer = timer ns = {} if isinstance(stmt, str): - stmt = reindent(stmt, 8) - if isinstance(setup, str): - setup = reindent(setup, 4) - src = template.format(stmt=stmt, setup=setup) - elif callable(setup): - src = template.format(stmt=stmt, setup='_setup()') - ns['_setup'] = setup - else: - raise ValueError("setup is neither a string nor callable") - self.src = src # Save for traceback display - code = compile(src, dummy_src_name, "exec") - exec(code, globals(), ns) - self.inner = ns["inner"] + self._setup = setup + self._stmt = stmt + self._create_inner(2) + self.inner = None elif callable(stmt): self.src = None if isinstance(setup, str): @@ -161,6 +156,30 @@ traceback.print_exc(file=file) + def _create_inner(self, number): + setup = self._setup + stmt = self._stmt + '\n' + loops, remainder = divmod(number, _unroll_number) + ns = {} + if isinstance(setup, str): + setup = reindent(setup, 4) + elif callable(setup): + ns['_setup'] = setup + setup = '_setup()' + else: + raise ValueError("setup is neither a string nor callable") + if loops: + loopbody = reindent(stmt, 8) * _unroll_number + loop = _loop_template.format(loopbody=loopbody) + else: + loop = '' + unrolled = reindent(stmt, 4) * remainder + src = _template.format(setup=setup, loop=loop, unrolled=unrolled) + self.src = src # Save for traceback display + code = compile(src, dummy_src_name, "exec") + exec(code, globals(), ns) + return ns["inner"] + def timeit(self, number=default_number): """Time 'number' executions of the main statement. @@ -171,11 +190,16 @@ to one million. The main statement, the setup statement and the timer function to be used are passed to the constructor. """ - it = itertools.repeat(None, number) + if self.inner is None: + inner = self._create_inner(number) + it = itertools.repeat(None, number // _unroll_number) + else: + inner = self.inner + it = itertools.repeat(None, number) gcold = gc.isenabled() gc.disable() try: - timing = self.inner(it, self.timer) + timing = inner(it, self.timer) finally: if gcold: gc.enable()