19

While trying to tackle a more complex problem, I came to compare access speed to local variable vs member variables.

Here a test program:

#!/usr/bin/env python

MAX=40000000

class StressTestMember(object):
    def __init__(self):
        self.m = 0

    def do_work(self):
        self.m += 1
        self.m *= 2

class StressTestLocal(object):
    def __init__(self):
        pass

    def do_work(self):
        m = 0
        m += 1
        m *= 2

# LOCAL access test
for i in range(MAX):
    StressTestLocal().do_work()

# MEMBER access test
for i in range(MAX):
    StressTestMember().do_work()

I know it might look like a bad idea to instantiate StressTestMember and StressTestLocal on each iterations but it makes sense in the modeled program where these are basically Active Records.

After a simple benchmark,

  • LOCAL access test: 0m22.836
  • MEMBER access test: 0m32.648s

The local version is ~33% faster while still part of a class. Why?

Tadeck
  • 132,510
  • 28
  • 152
  • 198
yadutaf
  • 6,840
  • 1
  • 37
  • 48

2 Answers2

32

self.m += 1 means you have to look up a local variable called self and then find the attribute called m

Of course if you just have to look up a local variable, it will be faster without the extra step.

It can be useful to look at what is happening under the hood:

>>> import dis
>>> dis.dis(StressTestLocal.do_work)
 18           0 LOAD_CONST               1 (0)
              3 STORE_FAST               1 (m)

 19           6 LOAD_FAST                1 (m)
              9 LOAD_CONST               2 (1)
             12 INPLACE_ADD         
             13 STORE_FAST               1 (m)

 20          16 LOAD_FAST                1 (m)
             19 LOAD_CONST               3 (2)
             22 INPLACE_MULTIPLY    
             23 STORE_FAST               1 (m)
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE        
>>> dis.dis(StressTestMember.do_work)
 10           0 LOAD_FAST                0 (self)
              3 DUP_TOP             
              4 LOAD_ATTR                0 (m)
              7 LOAD_CONST               1 (1)
             10 INPLACE_ADD         
             11 ROT_TWO             
             12 STORE_ATTR               0 (m)

 11          15 LOAD_FAST                0 (self)
             18 DUP_TOP             
             19 LOAD_ATTR                0 (m)
             22 LOAD_CONST               2 (2)
             25 INPLACE_MULTIPLY    
             26 ROT_TWO             
             27 STORE_ATTR               0 (m)
             30 LOAD_CONST               0 (None)
             33 RETURN_VALUE        
Preet Kukreti
  • 8,417
  • 28
  • 36
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • So does it follow that it'd be wise to create a new reference to a class variable in local scope? e.g., `m = self.m`? It wouldn't make any difference in this test, but my version of `do_work()` is a loop that runs millions of times. – James S Feb 26 '18 at 20:35
  • @JamesS, It's always best to measure your exact use case if it's important enough to tune it. If you were referencing `self.m` a few times I'd expect it to be faster to pull into a local var. Remember to save the local back to the attribute if you are mutating it. – John La Rooy Feb 27 '18 at 22:30
7

Local names is faster because Python does some optimization that local names don't need dict access, on the other hand, instance attributes need to access the __dict__ of the object.

This is also the reason why local names are faster than global names.

HYRY
  • 94,853
  • 25
  • 187
  • 187