Time to assign a variable in Python

1.6k views Asked by At

Suppose I have the following in a really tight loop:

a = func(x)
b = func2(a)

The variable a is not used anywhere else.

Does Python automatically compile away the assignment to a, or does it take the time to do the variable assignment every time? In other words, is this code identical, or is it marginally faster due to the lack of assigning to a?

b = func2(func(x))

Is the behavior the same for Python2.7 vs Python3?

4

There are 4 answers

0
Matthew Story On BEST ANSWER

So using the very fun dis module we can look into the actual bytecode that is generated from the python code you provided. To keep things simple I have replaced func and func2 with builtin functions (int and float).

So our source looks like this:

def assign():
    a = int()
    b = float(a)

Versus a simplified version:

def simple():
    b = float(int())

And then starting with the cpython 2.7 interpretter, we can see the bytecodes generated from the assign function:

dis.dis(assign)
  2           0 LOAD_GLOBAL              0 (int)
              3 CALL_FUNCTION            0
              6 STORE_FAST               0 (a)

  3           9 LOAD_GLOBAL              1 (float)
             12 LOAD_FAST                0 (a)
             15 CALL_FUNCTION            1
             18 STORE_FAST               1 (b)
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE

As you can see there is no peephole optimization to remove the unnecessary intermediate variable, which results in an additional 2 instructions (STORE_FAST a, LOAD_FAST a) when compared against the bytecodes for the simplified `simple method:

dis.dis(simple)
  2           0 LOAD_GLOBAL              0 (float)
              3 LOAD_GLOBAL              1 (int)
              6 CALL_FUNCTION            0
              9 CALL_FUNCTION            1
             12 STORE_FAST               0 (b)
             15 LOAD_CONST               0 (None)
             18 RETURN_VALUE

This is the same for the CPython interpreter for Python 3.5, and for the pypy interpreter for Python 2.7.

2
Riken Shah On

Such type of queries could be easily checked with timeit. Here are the results with Python2.7.

root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
1000000 loops, best of 3: 0.29 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
1000000 loops, best of 3: 0.284 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
1000000 loops, best of 3: 0.285 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
1000000 loops, best of 3: 0.283 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; a=f1(2); b=f2(a)"
1000000 loops, best of 3: 0.294 usec per loop
root:/var# python -m timeit "f1 = lambda x:x; f2 = lambda x: x*2; b=f2(f1(2))"
1000000 loops, best of 3: 0.286 usec per loop

And this shows consistent results with other answers that describe the use the the awesome dis module.

1
wwii On

Use the dis module to compare the bytecode: looks like the second method produces fewer operations

import dis

print(dis.dis('a=f(2);b=g(a)'))
print(dis.dis('b=g(f(2))'))


>>>   
  1           0 LOAD_NAME                0 (f)
              2 LOAD_CONST               0 (2)
              4 CALL_FUNCTION            1
              6 STORE_NAME               1 (a)
              8 LOAD_NAME                2 (g)
             10 LOAD_NAME                1 (a)
             12 CALL_FUNCTION            1
             14 STORE_NAME               3 (b)
             16 LOAD_CONST               1 (None)
             18 RETURN_VALUE
None
  1           0 LOAD_NAME                0 (g)
              2 LOAD_NAME                1 (f)
              4 LOAD_CONST               0 (2)
              6 CALL_FUNCTION            1
              8 CALL_FUNCTION            1
             10 STORE_NAME               2 (b)
             12 LOAD_CONST               1 (None)
             14 RETURN_VALUE
None
2
manuignatius On

The actual time will depend on the functions func() and func2() do. Not the best example, but a quick (and dirty) test code is given below:

import time

def func(x):
    return 5

def func2(a):
    return 10

t0 = time.time()
x = 10
for i in range(1,10000):
    a = func(x)
    b = func2(a)
t1 = time.time()

print("Time 1: ", t1-t0)

t2 = time.time()
x = 10
for i in range(1,10000):
    b = func2(func(x))
t3 = time.time()

print("Time 2: ", t3-t2)

The output of the above code is:

Time 1:  0.0029211044311523438
Time 2:  0.002785921096801758

So yes, the implementation where we avoid the assignment of a is marginally faster in Pyhton 3.