8

I saw a comment that lead me to the question Why does Python code run faster in a function?.

I got to thinking, and figured I would try it myself using the timeit library, however I got very different results:

(note: 10**8 was changed to 10**7 to make things a little bit speedier to time)

>>> from timeit import repeat
>>> setup = """
def main():
    for i in xrange(10**7):
        pass
"""
>>> stmt = """
for i in xrange(10**7):
    pass
"""
>>> min(repeat('main()', setup, repeat=7, number=10))
1.4399558753975725
>>> min(repeat(stmt, repeat=7, number=10))
1.4410973942722194
>>> 1.4410973942722194 / 1.4399558753975725
1.000792745732109
  • Did I use timeit correctly?
  • Why are these results less 0.1% different from each other, while the results from the other question were nearly 250% different?
  • Does it only make a difference when using CPython compiled versions of Python (like Cython)?
  • Ultimately: is Python code really faster in a function, or does it just depend on how you time it?
Community
  • 1
  • 1
Wesley Baugh
  • 3,720
  • 4
  • 24
  • 42
  • 1
    I think this is largely dependent on the implementation (so both version and distribution (i.e. normal Python vs CPython etc.) are important). Also, you should try running the exact same code as in that other question (and timing it the same way), just for a proper comparison. – Cornstalks Mar 08 '13 at 05:47
  • 1
    [This post](http://stackoverflow.com/a/11241708/1961486) is insightful. – Octipi Mar 08 '13 at 05:53
  • So, it sounds like it matters in compiled versions of Python (CPython, PyPy, etc.), but in plain old vanilla Python it doesn't make a lick of difference! – Wesley Baugh Mar 08 '13 at 06:04
  • 2
    @WesleyBaugh: Plain old vanilla Python **is** CPython. – Dietrich Epp Mar 08 '13 at 07:09
  • @DietrichEpp That's true! I was actually thinking of Cython (though, now I'm not sure if that makes much of a difference in this case). – Wesley Baugh Mar 08 '13 at 07:13

2 Answers2

11

The flaw in your test is the way timeit compiles the code of your stmt. It's actually compiled within the following template:

template = """
def inner(_it, _timer):
    %(setup)s
    _t0 = _timer()
    for _i in _it:
        %(stmt)s
    _t1 = _timer()
    return _t1 - _t0
"""

Thus stmt is actually running in a function, using the fastlocals array (i.e. STORE_FAST).

Here's a test with your function in the question as f_opt versus the unoptimized compiled stmt executed in the function f_no_opt:

>>> code = compile(stmt, '<string>', 'exec')
>>> f_no_opt = types.FunctionType(code, globals())

>>> t_no_opt = min(timeit.repeat(f_no_opt, repeat=10, number=10))
>>> t_opt = min(timeit.repeat(f_opt, repeat=10, number=10))
>>> t_opt / t_no_opt
0.4931101445632647
Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
  • This is great, and shows an advanced understanding of the inner workings of Python. In your experience, does this influence how we should code (during an optimization stage)? – Wesley Baugh Mar 08 '13 at 07:25
  • 2
    Good design already calls for using functions and minimizing your use of global variables, but definitely avoid accessing global variables in tight loops that need speed. You can find more advice in the wiki: [PythonSpeed](http://wiki.python.org/moin/PythonSpeed). – Eryk Sun Mar 08 '13 at 07:55
1

It comes down to compiler optimization algorithms. When performing Just-in-time compilation, it is much easier to identify frequently used chunks of code if they're found in functions.

The efficiency gains really would depend on the nature of the tasks being performed. In the example you gave, you aren't really doing anything computationally intensive, leaving fewer opportunities to achieve gains in efficiency through optimization.

As others have pointed out, however, CPython does not do just-in-time compilation. When code is compiled, however, C compilers will often execute them faster.

Check out this document on the GCC compiler: http://gcc.gnu.org/onlinedocs/gcc/Inline.html

Ashwin Balamohan
  • 3,303
  • 2
  • 25
  • 47
  • It sounds like this is true for CPython (since it is compiled), but not for vanilla Python since it is not. – Wesley Baugh Mar 08 '13 at 06:03
  • 1
    @WesleyBaugh: Wait... isn't CPython the same as "vanilla" Python? Or is "vanilla" python something else? – Dietrich Epp Mar 08 '13 at 07:08
  • 1
    CPython barely does any optimization. The extent of optimization is eliminating some useless loads and stores. CPython has no JIT, and it simply executes bytecode sequentially as an interpreter. (The same is not true for many other implementations, e.g. PyPy, IronPython or Jython, which all have JITs backing them). – nneonneo Mar 08 '13 at 07:08
  • (-1: this answer is too general to be useful in a Python-specific question, and is in fact wrong in the context of the most popular Python inteprerter.) – nneonneo Mar 08 '13 at 07:08
  • @DietrichEpp Thanks for the correction. I think I was thinking of Cython. – Wesley Baugh Mar 08 '13 at 07:14
  • nneonneo, I edited the post. I didn't intend to be misleading. This is how I understand the process, but would be happy to learn why/how I'm mistaken. – Ashwin Balamohan Mar 08 '13 at 07:26
  • Dude, GCC has nothing to do with running Python code. (Unless it's Cython, and that isn't Python.) My downvote remains. Please add something that is specific to Python, or at least prove you know something about Python that would help answer the question! – nneonneo Mar 08 '13 at 09:25