views:

444

answers:

4

Sorry because this is a noob question.

I'm reading Dive into Python, and just read "tuple is faster than list".

http://diveintopython3.org/native-datatypes.html#tuples

Tuple is immutable, and list is mutable, but I don't quite understand why tuple is faster.

Anyone did a performance test on this?

Thanks.

+3  A: 

Essentially because tuple's immutability means that the interpreter can use a leaner, faster data structure for it, compared to list.

Dan Breslau
+9  A: 

With the power of the timeit module, you can often resolve performance related questions yourself:

$ python2.6 -mtimeit -s 'a = tuple(range(10000))' 'for i in a: pass'
10000 loops, best of 3: 189 usec per loop
$ python2.6 -mtimeit -s 'a = list(range(10000))' 'for i in a: pass' 
10000 loops, best of 3: 191 usec per loop

This shows that tuple is negligibly faster than list for iteration. I get similar results for indexing, but for construction, tuple destroys list:

$ python2.6 -mtimeit '(1, 2, 3, 4)'   
10000000 loops, best of 3: 0.0266 usec per loop
$ python2.6 -mtimeit '[1, 2, 3, 4]'
10000000 loops, best of 3: 0.163 usec per loop

So if speed of iteration or indexing are the only factors, there's effectively no difference, but for construction, tuples win.

Alec Thomas
can you please rewrite your code in Python 3.0, so I can test it? :)
Vimvq1987
@Vimvq1987 Did you try that code with Python 3.0 before asking for a re-write?
gotgenes
Almost nobody uses Python 3. Install 2.x, don't expect people to jump hoops for you.
Glenn Maynard
@Glenn The code runs perfectly fine in Python 3. @Vimvq1987 would have known that had he *tried* it *before* asking.
gotgenes
ahhh, I forgot to import timeit, sorry, I'm new with this
Vimvq1987
It's work in 3.0 as well, just change to call 3.0 python.
shiki
The only case I see where tuples are significantly faster is constructing them, and that's largely the point--tuples are used a lot for returning multiple values from a function, so they're optimized for that case. In general, the choice to use tuples isn't based on performance. (In quick tests, tuples are actually about 20% slower than lists for indexing; I havn't bothered to look into why.)
Glenn Maynard
>>> import timeit>>> -mtimeit -s 'a = tuple(range(10000))' 'for i in a: pass'SyntaxError: invalid syntaxcan anyone please tell my what's wrong here?
Vimvq1987
Run the command Alec gave you on the **command line** (DOS if you're on Windows).
Adam Bernier
@Glenn Good point. Updated with timings for small tuple/list construction.
Alec Thomas
It's also worth noting that you save (at least on my machine) .0001 usec by leaving off the parentheses =P.
Wilduck
+13  A: 

The reported "speed of construction" ratio only holds for constant tuples (ones whose items are expressed by literals). Observe carefully (and repeat on your machine -- you just need to type the commands at a shell/command window!)...:

$ python3.1 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.379 usec per loop
$ python3.1 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.413 usec per loop

$ python3.1 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.174 usec per loop
$ python3.1 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0602 usec per loop

$ python2.6 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.352 usec per loop
$ python2.6 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.358 usec per loop

$ python2.6 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.157 usec per loop
$ python2.6 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0527 usec per loop

I didn't do the measurements on 3.0 because of course I don't have it around -- it's totally obsolete and there is absolutely no reason to keep it around, since 3.1 is superior to it in every way (Python 2.7, if you can upgrade to it, measures as being almost 20% faster than 2.6 in each task -- and 2.6, as you see, is faster than 3.1 -- so, if you care seriously about performance, Python 2.7 is really the only release you should be going for!).

Anyway, the key point here is that, in each Python release, building a list out of constant literals is about the same speed, or slightly slower, than building it out of values referenced by variables; but tuples behave very differently -- building a tuple out of constant literals is typically three times as fast as building it out of values referenced by variables! You may wonder how this can be, right?-)

Answer: a tuple made out of constant literals can easily be identified by the Python compiler as being one, immutable constant literal itself: so it's essentially built just once, when the compiler turns the source into bytecodes, and stashed away in the "constants table" of the relevant function or module. When those bytecodes execute, they just need to recover the pre-built constant tuple -- hey presto!-)

This easy optimization cannot be applied to lists, because a list is a mutable object, so it's crucial that, if the same expression such as [1, 2, 3] executes twice (in a loop -- the timeit module makes the loop on your behalf;-), a fresh new list object is constructed anew each time -- and that construction (like the construction of a tuple when the compiler cannot trivially identify it as a compile-time constant and immutable object) does take a little while.

That being said, tuple construction (when both constructions actually have to occur) still is about twice as fast as list construction -- and that discrepancy can be explained by the tuple's sheer simplicity, which other answers have mentioned repeatedly. But, that simplicity does not account for a speedup of six times or more, as you observe if you only compare the construction of lists and tuples with simple constant literals as their items!_)

Alex Martelli
the answer I've long waited :)
Vimvq1987
+1  A: 

Alex gave a great answer, but I'm going to try to expand on a few things I think worth mentioning. Any performance differences are generally small and implementation specific: so don't bet the farm on them.

In CPython, tuples are stored in a single block of memory, so creating a new tuple involves at worst a single call to allocate memory. Lists are allocated in two blocks: the fixed one with all the Python object information and a variable sized block for the data. That's part of the reason why creating a tuple is faster, but it probably also explains the slight difference in indexing speed as there is one fewer pointer to follow.

There are also optimisations in CPython to reduce memory allocations: de-allocated list objects are saved on a free list so they can be reused, but allocating a non-empty list still requires a memory allocation for the data. Tuples are saved on 20 free lists for different sized tuples so allocating a small tuple will often not require any memory allocation calls at all.

Optimisations like this are helpful in practice, but they may also make it risky to depend too much on the results of 'timeit' and of course are completely different if you move to something like IronPython where memory allocation works quite differently.

Duncan
"one fewer pointer to follow" -- not so; while there are differences in memory allocation, the functions to get a particular item are (after stripping out error checking) identical: `PyObject * PyBLAH_GetItem(PyObject *op, Py_ssize_t i) {return ((PyBLAHObject *)op) -> ob_item[i];}`
John Machin
Yes so. In the data structure for a tuple `ob_item` is an array at the end of the structure. In a list `ob_item` is a pointer to an array. The C code to access an element of either array is identical but in the case of a list there is an extra memory read to fetch the value of the pointer.
Duncan
You are correct. tupleobject.h has `PyObject * ob_item[1];` listobject.h has `PyObject ** ob_item;`
John Machin