5 ms·
Python code optimization is actually tricky ground where analogies from other languages (like C/C++) don't necessarily apply. One of the more surprising result
by Xion 14y ago
Python code optimization is actually tricky ground where analogies from other languages (like C/C++) don't necessarily apply.
One of the more surprising results (esp. for non-pythonists) is the fact that string formatting:
s = "%s" % some_integer
is faster than "casting" to string:
s = str(some_intenger)
That's solely because looking up the 'str' symbol requires finding an element in global symbols' hashtable. This turns out to be more expensive than parsing the format string and building the result of % operator.
- fauigerzigerk 14y agoApparently there's a function call overhead as well that the % operator doesn't incur because for i in xrange(10000000): s = "%s" % i is also faster than lstr = str for i in xrange(10000000): s = lstr(i) [Edit] After thinking about it a second longer, I wonder whether there is some lookup for lstr as well even though it's local. But storing the function in lstr is faster than using str so I'm not sure how this is actually implemented. I'm sure someone here will know more.
- njharman 14y agoYes there is lookup, always lookup. Dynamic language, something in loop could change what lstr is. Each "dot" incurs lookup. Your example reminds me of one idiom. Assigning nested.look.up to local var for access inside loop.
- dmorgan 14y ago>Yes there is lookup, always lookup. Dynamic language, something in loop could change what lstr is. And why is that presented as something inevitable? The interpreter/compiler could analyze that part and see that the function/name is not changed during the loop, and cache for that. I'd guess that PyPy tries to do it that way, anyway...
- lvh 14y agoYes, that's one of the many optimizations PyPy does, however it's only a very limited one. PyPy goes much, much deeper than this, with tools like function inlining and escape analysis.
- jemfinch 14y agoYou need to make sure you do all your benchmarks in a function. In a funtion, local variables translate to indexes into a local variable array; outside of a function, they remain globals lookups. Your first reply that "there's always a lookup" isn't right. Some variable accesses (specifically: local variable accesses) do simply map to array accesses.
- pjscott 14y agoCPython compiles to bytecode, turning all local variable references into offsets into a local variable array. So, getting the "str" function requires a global name lookup, while getting your local "lstr" only requires an array lookup with a constant embedded in the bytecode. You can have a lot of fun playing with the dis module to look at the bytecode that Python generates for your functions: http://docs.python.org/library/dis.html http://docs.python.org/library/dis.html
- jedbrown 14y agoThis is not a robust observation python3.2: >>> timeit.timeit("for i in range(100): s = '%s' % (i,)", number=100000) 2.868873119354248 >>> timeit.timeit("for i in range(100): s = '%s' % i", number=100000) 2.615748882293701 >>> timeit.timeit("for i in range(100): s = str(i)", number=100000) 2.4016571044921875 >>> timeit.timeit("for i in range(100): s = i.__str__()", number=100000) 1.8993198871612549 python2.7: >>> timeit.timeit("for i in xrange(100): s = '%s' % (i,)", number=100000) 1.9474480152130127 >>> timeit.timeit("for i in xrange(100): s = '%s' % i", number=100000) 1.6135330200195312 >>> timeit.timeit("for i in xrange(100): s = str(i)", number=100000) 2.009705066680908 >>> timeit.timeit("for i in xrange(100): s = i.__str__()", number=100000) 1.539802074432373
- Xion 14y agoIt would seem that loop bookkeeping dominates execution time here. Without it, I get quite different results: >>> timeit.timeit("'%s' % i", setup="i = 42", number=1000000) 0.31799793243408203 >>> timeit.timeit("str(i)", setup="i = 42", number=1000000) 0.4146881103515625 This is another argument for profiling everything.
- jedbrown 14y agoOn the contrary, the time per inner iteration is slightly higher this way and the same conclusions hold: 1. python3.2 str() is at least as fast as '%s' formatting 2. '%s' is slightly faster than str() with python2.7 3. theint.__str__() is faster than either alternative in all cases
- zurn 14y agoAnd also, for using timeit correctly. The command line usage is handy too: python -m timeit -s "i=42" "str(i)"