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,
- Method attributes, and
- Function (non-method) attributes?
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
All the code is at the bottom of this post.
Results
| | | | Relative |
| Where | Time | Rate | Performance |
| LV Local Variable | 1.92 | 0.5208 | 100% |
| GV Global Variable | 1.99 | 0.5020 | 96% |
| ISA Instance Slotted Attribute | 2.09 | 0.4789 | 92% |
| CA Class Attribute | 3.12 | 0.3204 | 62% |
| INSA Instance Non-Slotted Attribute | 3.28 | 0.3051 | 59% |
| FA Function Attribute | 3.49 | 0.2865 | 55% |
| MA Method Attribute | 6.29 | 0.1589 | 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.
The performance of the different types of variables clustered into three groupings.
- Locals, globals, and class instance slotted variables: Global variables are 96% as fast as locals, while class instance slotted attributes are 92% as fast.
- Class Attribute, non-slotted class instance attribute, and function attribute: These perform, respectively, at 62%, 59%, and 55% of a local variable.
- Method attribute: Using a method attribute is in a class of its own, at 31% of the performance of a local variable.
Conclusions
It is worthwhile to use slot attributes for performance reasons, not merely for code quality reasons
The surprise is that compared to common wisdom, globals are second best, better even than slotted instance attributes in a class. Another surprise is that method attributes are the worst.
Code
#!/bin/python3.12
# test performance differences among accessing a global variable, class, instance-slot attribute, instance-nonslot atribute, function attribute, or local variable.
from timeit import timeit
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):
#self.classfun.FA=self.INSA+1 # NOT. The function is of the class, not the instance
# setattr(TestClass.classfun, "FA", self.INSA+1 ) # or directly:
TestClass.classfun.MA=self.INSA+1
LV=TestClass.classfun.MA+1
print(GV, self.CA, self.ISA, self.INSA, self.classfun.MA, LV)
# NOT: setattr(self.classfun, "FA", self.INSA+1 )
# NOT: LV=self.classfun.FA+1
print(GV, self.CA, self.ISA, self.INSA, self.classfun.MA, LV)
def testGV(self):
s=0
for i in range(10000000):
s+=GV
return s
def testCA(self):
s=0
for i in range(10000000):
s+=self.CA
return s
def testISA(self):
s=0
for i in range(10000000):
s+=self.ISA
return s
def testINSA(self):
s=0
for i in range(10000000):
s+=self.INSA
return s
def testMA(self): #self.classfun.MA, LV
s=0
for i in range(10000000):
s+=self.classfun.MA
return s
def testLV(self):
s=0
LV=TestClass.classfun.MA+1
for i in range(10000000):
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(10000000):
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()
print(instance.__slots__)
instance.foo="foo"
instance.classfun()
print(instance.__dict__)
print(" GV",instance.testGV(), f"{timeit('instance.testGV()',number=10,globals=globals()):.3f}" )
print(" CA",instance.testCA(), f"{timeit('instance.testCA()',number=10,globals=globals()):.3f}")
print(" ISA",instance.testISA(), f"{timeit('instance.testISA()',number=10,globals=globals()):.3f}" )
print("INSA",instance.testINSA(), f"{timeit('instance.testINSA()',number=10,globals=globals()):.3f}" )
print(" MA",instance.testMA(), f"{timeit('instance.testMA()',number=10,globals=globals()):.3f}" )
print(" LV",instance.testLV(), f"{timeit('instance.testLV()',number=10,globals=globals()):.3f}" )
print(" FA",testFA(instance), f"{timeit('testFA(instance)',number=10,globals=globals()):.3f}" )
"""
| | | |Relative|
|Where |Time |Rate |Performance|
|LV Local Variable |1.92 |0.5208 |100%|
|GV Global Variable |1.99 |0.5020 |96%|
|ISA Instance Slotted Attribute |2.09 |0.4789 |92%|
|CA Class Attribute |3.12 |0.3204 |62%|
|INSA Instance Non-Slotted Attribute |3.28 |0.3051 |59%|
|FA Function Attribute |3.49 |0.2865 |55%|
|MA Method Attribute |6.29 |0.1589 |31%|
"""
Top comments (1)
| I tried to use a table for the results,| but the markdown did not transform into a table | Sorry about the misalignment.