Assignment saves time?

R

rpglover64

In timing my code, which I probably screwed up, I found something
curious:
given that li=range(1000),

the code block (enclosed in braces for clarification)
{
size=len(li)
size==1000
}
runs slightly faster than
{
len(li)==1000
}

I tested this using the timeit module, and though the difference was
small, I would have expected the first code block to do slightly
worse, because it has to assign the length to a variable and then call
the variable (still having to compute the length and having either one
or two additional operations to perform).

I would appreciate it if more knowledgeable coders could
a) point out why I'm wrong and show me how to test it correctly. or
b) explain why this occurs in terminology understandable to someone
who knows next to nothing about the internal workings of python.

I highly appreciate your any response I may receive. Thank you in
advance.
 
H

Hrvoje Niksic

I tested this using the timeit module, and though the difference was
small, I would have expected the first code block to do slightly
worse,

Can you post your entire benchmark, so that we can repeat it? When I
tried the obvious, I got the expected result:

$ python -m timeit -s 'l=[]' 'len(l)==1000'
1000000 loops, best of 3: 0.256 usec per loop
$ python -m timeit -s 'l=[]' 'len(l)==1000'
1000000 loops, best of 3: 0.27 usec per loop

$ python -m timeit -s 'l=[]' 's=len(l); s==1000'
1000000 loops, best of 3: 0.287 usec per loop
$ python -m timeit -s 'l=[]' 's=len(l); s==1000'
1000000 loops, best of 3: 0.299 usec per loop
 
C

cokofreedom

$ python -m timeit -s 'l=[]' 'len(l)==1000'
1000000 loops, best of 3: 0.256 usec per loop
$ python -m timeit -s 'l=[]' 'len(l)==1000'
1000000 loops, best of 3: 0.27 usec per loop

$ python -m timeit -s 'l=[]' 's=len(l); s==1000'
1000000 loops, best of 3: 0.287 usec per loop
$ python -m timeit -s 'l=[]' 's=len(l); s==1000'
1000000 loops, best of 3: 0.299 usec per loop

More results pretty much agree with yours:

C:\Python25>python -m timeit -s 'l=range(1000)' 'len(l)==1000'
10000000 loops, best of 3: 0.0235 usec per loop

C:\Python25>python -m timeit -s 'l=range(1000)' 'len(l)==1000'
10000000 loops, best of 3: 0.0245 usec per loop

C:\Python25>python -m timeit -s 'l=range(1000)' 's=len(l)' 's==1000'
10000000 loops, best of 3: 0.0383 usec per loop

C:\Python25>python -m timeit -s 'l=range(1000)' 's=len(l)' 's==1000'
10000000 loops, best of 3: 0.038 usec per loop
 
R

rpglover64

I ran it in an interactive python shell, which might have made a
difference.

import timeit
time1=timeit.Timer('s=len(li); s==1000', 'li=range(1000)')
time2=timeit.Timer('len(li)==1000', 'li=range(1000)')
store=min(time1.repeat(5))
call=min(time2.repeat(5))
store=min(min(time1.repeat(5)),store)
call=min(min(time2.repeat(5)),call)
store
0.25531911849975586
call
0.25700902938842773

The difference is small enough to be insignificant, but I am curious
how it's possible and why it happened. It's more likely a reflection
of how I timed it than anything else, isn't it?
 
R

rpglover64

I ran it in an interactive python shell, which might have made a
difference.

import timeit
time1=timeit.Timer('s=len(li); s==1000', 'li=range(1000)')
time2=timeit.Timer('len(li)==1000', 'li=range(1000)')
store=min(time1.repeat(5))
call=min(time2.repeat(5))
store=min(min(time1.repeat(5)),store)
call=min(min(time2.repeat(5)),call)
store
0.25531911849975586
call
0.25700902938842773

The difference is small enough to be insignificant, but I am curious
how it's possible and why it happened. It's more likely a reflection
of how I timed it than anything else, isn't it?
 
G

Gabriel Genellina

I ran it in an interactive python shell, which might have made a
difference.

import timeit
time1=timeit.Timer('s=len(li); s==1000', 'li=range(1000)')
time2=timeit.Timer('len(li)==1000', 'li=range(1000)')
store=min(time1.repeat(5))
call=min(time2.repeat(5))
store=min(min(time1.repeat(5)),store)
call=min(min(time2.repeat(5)),call)
store
0.25531911849975586
call
0.25700902938842773

The difference is small enough to be insignificant, but I am curious
how it's possible and why it happened. It's more likely a reflection
of how I timed it than anything else, isn't it?

Yes, I think it's an artifact of how you measured it. I've tried this:

import timeit
time1=timeit.Timer('s=len(li); s==1000', 'li=range(1000)')
r1 = time1.repeat(5)
print 'store', min(r1), r1
time2=timeit.Timer('len(li)==1000', 'li=range(1000)')
r2 = time2.repeat(5)
print 'call', min(r2), r2

and got the expected results:

store 0.336916842783 [0.33712636661922118, 0.34131209413486907,
0.33691684278309109, 0.33726828409755982, 0.34154312908484186]

call 0.296055783626 [0.29648125669603265, 0.29605578362613105, 0
..29716346630647195, 0.29883546651878934, 0.29798368228364236]
 
S

Steven D'Aprano

I ran it in an interactive python shell, which might have made a
difference.

import timeit
time1=timeit.Timer('s=len(li); s==1000', 'li=range(1000)')
time2=timeit.Timer('len(li)==1000', 'li=range(1000)')
store=min(time1.repeat(5))
call=min(time2.repeat(5))
store=min(min(time1.repeat(5)),store)
call=min(min(time2.repeat(5)),call)
store
0.25531911849975586
call
0.25700902938842773

The difference is small enough to be insignificant, but I am curious how
it's possible and why it happened. It's more likely a reflection of how
I timed it than anything else, isn't it?


No. It's more likely to be a reflection of the fact that on a modern,
multitasking operating system, there are a lot of processes running, and
timings can be unpredictable because you have little control over those
other processes. In other words: it was probably a fluke.

Does your test *consistently* give you that result? Over and over again?

If so, then I would be wondering if there is some sort of weird caching
or pipelining issue in your CPU, where for some reason the first result
actually completes faster despite needing to do more work.
1 0 LOAD_NAME 0 (len)
3 LOAD_NAME 1 (li)
6 CALL_FUNCTION 1
9 LOAD_CONST 0 (1000)
12 COMPARE_OP 2 (==)
15 RETURN_VALUE


None of those ops correspond to machine instructions. There's lots of
opportunity for weird interactions between Python and the underlying
hardware. I wouldn't expect them, but nor would I discount the
possibility.
 
R

rpglover64

I ran the following in the python shell:
import timeit
storeit=timeit.Timer('s=len(li);s==1000', 'li=range(1000)')
checkit=timeit.Timer('len(li)==1000', 'li=range(1000)')
listofresults=[(min(storeit.repeat(5)),min(checkit.repeat(5))) for i in xrange(20)]

listofresults contained these values

(0.25506401062011719, 0.25763511657714844)
(0.2541499137878418, 0.25717496871948242)
(0.25352811813354492, 0.25570392608642578)
(0.25349903106689453, 0.25812816619873047)
(0.25426197052001953, 0.25819206237792969)
(0.25236606597900391, 0.25693607330322266)
(0.25433588027954102, 0.25758695602416992)
(0.25356602668762207, 0.25770902633666992)
(0.25328993797302246, 0.25844502449035645)
(0.25414395332336426, 0.25747895240783691)
(0.25800919532775879, 0.25954699516296387)
(0.25691604614257812, 0.25997209548950195)
(0.25767397880554199, 0.25859308242797852)
(0.25512409210205078, 0.26129603385925293)
(0.25843596458435059, 0.25791501998901367)
(0.2594301700592041, 0.2605140209197998)
(0.25701212882995605, 0.25839614868164062)
(0.25591516494750977, 0.26186609268188477)
(0.25582718849182129, 0.25893092155456543)
(0.2576749324798584, 0.25799012184143066)

in only one of these pairs is the first value larger than the second
value. I don't think this constitutes consistently, but I do think it
counts as frequently.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top