DEV Community

David A. Kra
David A. Kra

Posted on • Edited on

How much better are python local variables over globals, attributes, or slots?

Alternatively, how much worse are the others compared to local variables?

What's the big deal?

Some articles advise python programmers to use local variables if they will be used a lot, even if only referenced but not changed.

For example, here are two quotes from "Making Python Programs Blazing Fast" :

"There's actually difference in speed of lookup even between - let's say - local variable in function (fastest), class-level attribute (e.g. self.name - slower) and global for example imported function like time.time (slowest)."

"Don't Access Attributes

Another thing that might slow down your programs is dot operator (.) which is used when accessing object attributes. This operator triggers dictionary lookup using getattribute, which creates extra overhead in your code. "

Also, these are not the only possibilities. What about differences among:

  • Class attributes,
  • Instance attributes,
  • Slot Instance attributes,
  • Slot Instance attributes of a different instance
  • Method attributes,
  • Function (non-method) attributes,
  • And as compared to using a constant?

Testing

I tested by using all these in loops with 1,000,000 iterations, but minimal work per iteration inside them. The functions were all methods of a class, except for the function used to test function attributes for functions outside a class.

A typical function was of the form:

      def testINSA(self):
          s=0
          for i in range(10000000):
            s+=self.INSA # what is after the = differs in each function.
          return s    

Enter fullscreen mode Exit fullscreen mode

All the code is at the bottom of this post.

Results

Addend location Avg time of 10 calls Rate per Second Relative Performance
LV Local Variable 1.0253 0.975 100%
C Constant 1.0896 0.918 94%
ISA Instance Slotted Attribute          1.1156 0.896 92%
GV Global Variable 1.1293 0.886 91%
OISA Other Instance Slotted Attribute          1.2055 0.830 85%
CA Class Attribute 1.6385 0.610 63%
INSA Instance Non-Slotted Attribute     1.739 0.575 59%
FA Function Attribute 1.8076 0.553 57%
MA Method Attribute                     3.3259 0.301 31%

Explanation:
When comparing performance, always compare the (Achievements / Resource), such as Miles per Gallon, or Calculations per Second, rather than Liters per Km, or Seconds per Calculation.

  • Time is as reported by timeit, limited to 3 significant digits.
  • Rate is the reciprocal of time. How many of these per second.
  • Relative performance is the rate as compared to the best. By using relative performance, bigger is better, and twice as big is twice as good.

Findings

Yes, the local variables are the fastest. They are even faster than constants.

The performance of the different types of variables is clustered into groupings.

  1. Locals do perform the best.
  2. Constants, class instance slotted attributes, and global variables: Constants were 94% as fast, Class Instance slotted attributes were 92% as fast, while Global variables were 91% as fast as locals.
  3. Other Instance slotted attributes: These are slotted instance attributes of some other object. These performed 85% of a local variable.
  4. Class Attribute, Class Instance Non-Slotted Attribute, and Function Attribute: These performed, respectively, at 63%, 59%, and 57% of a local variable.
  5. Method attribute A method attribute performs in a class of its own, at 31% of the performance of a local variable.

Conclusions

If used millions of times, it is worthwhile to use local variables and slot attributes for performance reasons, not merely for code quality reasons.

The surprise is that compared to common wisdom, globals are almost as performant as slotted instance attributes in a class. Another surprise is that method attributes are the worst.

Code

#!/bin/python3.12
# published as https://dev.to/dakra137/how-much-better-are-python-local-variables-over-globals-attributes-or-slots-a2e 

# slots information
# see https://docs.python.org/3/reference/datamodel.html#slots 
# see also https://www.turing.com/kb/introduction-to-python-class-attributes 

# comments at https://dev.to/martinheinz/making-python-programs-blazing-fast-4knl 

# test performance differences among accessing a global variable, class, instance-slot attribute, other-instance slot attribute,  instance-nonslot atribute, function attribute, or local variable.

from timeit import timeit
iterations=10000000

GV=42 # Global Variable

class TestClass:
      __slots__=['ISA',  '__dict__'] # Instance slots

      CA=GV+1 # Class Attribute

      def __init__(self):
          self.ISA=self.CA+1 # instance slot attribute 
          self.INSA=self.ISA+1  # instance non-slot attribute 

      def classfun(self): # Only called once. set a method attribute
          TestClass.classfun.MA=self.INSA+1
          LV=TestClass.classfun.MA+1
          print(GV, self.CA, self.ISA, self.INSA, self.classfun.MA, LV)

      def testC(self):
          s=0
          for i in range(iterations):
            s+=1
          return s    


      def testGV(self):
          s=0
          for i in range(iterations):
            s+=GV
          return s    

      def testCA(self):
          s=0
          for i in range(iterations):
            s+=self.CA
          return s    

      def testISA(self):
          s=0
          for i in range(iterations):
            s+=self.ISA
          return s    

      def testOISA(self): # Other instance's ISA
          s=0
          for i in range(iterations):
            s+=instance2.ISA
          return s    

      def testINSA(self):
          s=0
          for i in range(iterations):
            s+=self.INSA
          return s    


      def testMA(self):  #self.classfun.MA, LV
          s=0
          for i in range(iterations):
            s+=self.classfun.MA
          return s    


      def testLV(self):
          s=0
          LV=TestClass.classfun.MA+1
          for i in range(iterations):
            s+=LV
          return s    

# # # # # # #  end of Class  # # # # #

def testFA(anobj):  # test a function attribute, not a method or class attribute.
          testFA.FA=anobj.classfun.MA+2
          s=0
          for i in range(iterations):
            s+=testFA.FA
          return s    

# test performance differences among accessing a global variable, class attribute, instance-slot attribute, instance-nonslot atribute, function attribute, or local variable.

if __name__ == "__main__": 
     instance = TestClass() 
     instance2 = TestClass() 
     print(instance.__slots__) 
     instance.foo="foo"
     instance.classfun()
     print(instance.__dict__)

     tin=10
     print("LV  ",instance.testLV(), f"{timeit('instance.testLV()',number=tin,globals=globals()):.3f}" )
     print("C   ",instance.testC(), f"{timeit('instance.testC()',number=tin,globals=globals()):.3f}" )
     print("ISA ",instance.testISA(), f"{timeit('instance.testISA()',number=tin,globals=globals()):.3f}" )     
     print("GV  ",instance.testGV(), f"{timeit('instance.testGV()',number=tin,globals=globals()):.3f}" )
     print("OISA",instance.testOISA(), f"{timeit('instance.testOISA()',number=tin,globals=globals()):.3f}" )
     print("CA  ",instance.testCA(), f"{timeit('instance.testCA()',number=tin,globals=globals()):.3f}")
     print("INSA",instance.testINSA(), f"{timeit('instance.testINSA()',number=tin,globals=globals()):.3f}" )
     print("FA  ",testFA(instance), f"{timeit('testFA(instance)',number=tin,globals=globals()):.3f}" )
     print("MA  ",instance.testMA(), f"{timeit('instance.testMA()',number=tin,globals=globals()):.3f}" )

quit()


Enter fullscreen mode Exit fullscreen mode

Top comments (1)