I am still new to Python, and I have been trying to improve the performance of my Python script, so I tested it with and without global variables. I timed it, and to my surp
When Python compiles a function, the function knows before it is called if the variables in it are locals, closures, or globals.
We have several ways of referencing variables in functions:
So let's create these kinds of variables in a few different functions so we can see for ourselves:
global_foo = 'foo'
def globalfoo():
return global_foo
def makeclosurefoo():
boundfoo = 'foo'
def innerfoo():
return boundfoo
return innerfoo
closurefoo = makeclosurefoo()
def defaultfoo(foo='foo'):
return foo
def localfoo():
foo = 'foo'
return foo
We can see that each function knows where to look up the variable - it doesn't need to do so at runtime:
>>> import dis
>>> dis.dis(globalfoo)
2 0 LOAD_GLOBAL 0 (global_foo)
2 RETURN_VALUE
>>> dis.dis(closurefoo)
4 0 LOAD_DEREF 0 (boundfoo)
2 RETURN_VALUE
>>> dis.dis(defaultfoo)
2 0 LOAD_FAST 0 (foo)
2 RETURN_VALUE
>>> dis.dis(localfoo)
2 0 LOAD_CONST 1 ('foo')
2 STORE_FAST 0 (foo)
3 4 LOAD_FAST 0 (foo)
6 RETURN_VALUE
We can see that currently the byte-code for a global is LOAD_GLOBAL, a closure variable is LOAD_DEREF, and a local is LOAD_FAST. These are implementation details of CPython, and may change from version to version - but it is useful to be able to see that Python treats each variable lookup differently.
Paste into an interpreter and see for yourself:
import dis
dis.dis(globalfoo)
dis.dis(closurefoo)
dis.dis(defaultfoo)
dis.dis(localfoo)
Test code (feel free to test on your system):
import sys
sys.version
import timeit
min(timeit.repeat(globalfoo))
min(timeit.repeat(closurefoo))
min(timeit.repeat(defaultfoo))
min(timeit.repeat(localfoo))
On Windows, at least in this build, it looks like closures get a little bit of a penalty - and using a local that's a default is the fastest, because you don't have to assign the local each time:
>>> import sys
>>> sys.version
'3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
>>> import timeit
>>> min(timeit.repeat(globalfoo))
0.0728403456180331
>>> min(timeit.repeat(closurefoo))
0.07465484920749077
>>> min(timeit.repeat(defaultfoo))
0.06542038103088998
>>> min(timeit.repeat(localfoo))
0.06801849537714588
On Linux:
>>> import sys
>>> sys.version
'3.6.4 |Anaconda custom (64-bit)| (default, Mar 13 2018, 01:15:57) \n[GCC 7.2.0]'
>>> import timeit
>>> min(timeit.repeat(globalfoo))
0.08560040907468647
>>> min(timeit.repeat(closurefoo))
0.08592104795388877
>>> min(timeit.repeat(defaultfoo))
0.06587386003229767
>>> min(timeit.repeat(localfoo))
0.06887826602905989
I'll add other systems as I have a chance to test them.