Skip to main content

Python Performance

Parity Testing

from timeit import Timer

t1 = Timer("for i in xrange(100): i % 2")
t2 = Timer("for i in xrange(100): i & 1")
# The "not" tests show what happens when interpreting
# the result as a boolean
t3 = Timer("for i in xrange(100): not i % 2")
t4 = Timer("for i in xrange(100): not i & 1")

print "Checking for odd parity with `mod`:\t%.4f" % t1.timeit()
print "Checking for odd parity with `and`:\t%.4f" % t2.timeit()
print "Checking for even parity with `mod`:\t%.4f" % t3.timeit()
print "Checking for even parity with `and`:\t%.4f" % t4.timeit()

MacPython 2.7.2

Checking for odd parity with `mod`:    6.5617
Checking for odd parity with `and`: 5.3778
Checking for even parity with `mod`:    8.4417
Checking for even parity with `and`:    7.4086

PyPy 1.6.0 (with GCC 4.0.1; Python 2.7.2)

Checking for odd parity with `mod`:    0.2556
Checking for odd parity with `and`: 0.2312
Checking for even parity with `mod`:    1.7576
Checking for even parity with `and`:    0.6614

The results for odd parity were murky. Sometimes mod was slightly faster; sometimes bitwise-and was faster. There was no question with the even parity, however: The bitwise-and operator played much more nicely with the `not` operator than did the mod operator.

Jython 2.5.2

Checking for odd parity with `mod`:    3.4480
Checking for odd parity with `and`: 1.9380
Checking for even parity with `mod`:    3.6050
Checking for even parity with `and`:    2.0440

Tuple Unpacking

from timeit import Timer

index1 = Timer("x = tpl[0]", "tpl = (5,)")
unpack1 = Timer("x, = tpl", "tpl = (5,)")
index2 = Timer("x = tpl[1]", "tpl = (5, 6)")
unpack2 = Timer("y, x = tpl", "tpl = (5, 6)")
index3 = Timer("x = tpl[2]", "tpl = (5, 6, 7)")
unpack3 = Timer("y, y, x = tpl", "tpl = (5, 6, 7)")
index4 = Timer("x = tpl[3]", "tpl = (5, 6, 7, 8)")
unpack4 = Timer("y, y, y, x = tpl", "tpl = (5, 6, 7, 8)")

list_index2 = Timer("[tpl[1] for tpl in tuples]", "tuples = [(i, i * i) for i in xrange(100)]")
list_unpack2 = Timer("[y for x, y in tuples]", "tuples = [(i, i * i) for i in xrange(100)]")
list_map2 = Timer("map(itemgetter(1), tuples)", "tuples = [(i, i * i) for i in xrange(100)]; from operator import itemgetter")

times = 100000000

print "Indexing vs. unpacking a 1-tuple:\t%.4f\t%.4f" % (index1.timeit(number=times), unpack1.timeit(number=times))
print "Indexing vs. unpacking a 2-tuple:\t%.4f\t%.4f" % (index2.timeit(number=times), unpack2.timeit(number=times))
print "Indexing vs. unpacking a 3-tuple:\t%.4f\t%.4f" % (index3.timeit(number=times), unpack3.timeit(number=times))
print "Indexing vs. unpacking a 4-tuple:\t%.4f\t%.4f" % (index4.timeit(number=times), unpack4.timeit(number=times))
print "Indexing vs. unpacking a list of 2-tuples:\t%.4f\t%.4f" % (list_index2.timeit(), list_unpack2.timeit())
print "map() and itemgetter() (just for kicks):\t%.4f" % (list_map2.timeit())

MacPython 2.7.2

Indexing vs. unpacking a 1-tuple:  5.0712  3.3939
Indexing vs. unpacking a 2-tuple:   5.7888  6.2801
Indexing vs. unpacking a 3-tuple:   6.1820  7.5976
Indexing vs. unpacking a 4-tuple:   7.1802  7.8219
Indexing vs. unpacking a list of 2-tuples:  8.6561  8.3513
map() and itemgetter() (just for kicks):    9.1651

Unpacking is slightly faster for a tuple of a single item. This happens more often than you might thing; consider, for example, `struct.unpack(">H")`, which returns a tuple. Thus, use `val, = struct.unpack(">H")` in these situations instead of `val = struct.unpack(">H")[0]`. That said, use with care, since tuple unpacking is also slightly more unreadable than indexing, and so it does not seem that tuple unpacking causes a bottleneck for our software... yet. As the tuple grows, however, indexing is always faster. Also, as one might have suspected, `itemgetter` works more slowly than a list comprehension.

PyPy 1.6.0 (with GCC 4.0.1; Python 2.7.2)

Indexing vs. unpacking a 1-tuple:  0.2268  0.2279
Indexing vs. unpacking a 2-tuple:   0.2301  0.2302
Indexing vs. unpacking a 3-tuple:   0.2335  0.2320
Indexing vs. unpacking a 4-tuple:   0.2332  0.2344
Indexing vs. unpacking a list of 2-tuples:  1.2610  1.2698
map() and itemgetter() (just for kicks):    5.4586

There is no clear difference in pypy; both the indexing and unpacking operations seem to vary constantly. (I tested informally using a 250 item tuple. My test with 1000 slowed down unpacking considerably, but I suspect the bottleneck was with the source code parser, not the operation itself.) It is clear that map and itemgetter are significantly slower for pypy, however.

Jython 2.5.2

Indexing vs. unpacking a 1-tuple:  0.6510  1.1520
Indexing vs. unpacking a 2-tuple:   0.9610  0.7800
Indexing vs. unpacking a 3-tuple:   0.8930  0.8330
Indexing vs. unpacking a 4-tuple:   1.0250  0.8070
Indexing vs. unpacking a list of 2-tuples:  36.4800 40.4600
map() and itemgetter() (just for kicks):    11.0170

The Jython results varied from run to run, but it looks like unpacking was almost always faster. It also looks like Jython does not handle list comprehensions very well. Now you know.

Powers of Two

from timeit import Timer

# Use 62 to prevent slowdown from long ints
t1 = Timer("for i in xrange(62): 1 << i")
t2 = Timer("for i in xrange(62): 2 ** i")

times = 1000000

print "Bit-shifting vs. Exponentation:\t%.4f\t%.4f" % (t1.timeit(number=times), t2.timeit(number=times))

MacPython 2.7.2

Bit-shifting vs. Exponentation:    3.8654  8.3995

Bit-shifting wins by a longshot.

PyPy 1.6.0 (with GCC 4.0.1; Python 2.7.2)

Bit-shifting vs. Exponentation:    0.2184  2.0279

Again, bit-shifting wins by a longshot.

Jython 2.5.2

Bit-shifting vs. Exponentation:    2.6870  17.0960

Don't use Jython.