Performance with global variables vs local

后端 未结 4 615
囚心锁ツ
囚心锁ツ 2020-11-30 08:57

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

4条回答
  •  庸人自扰
    2020-11-30 09:10

    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:

    • globals
    • closures
    • locals

    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
    

    Dissassembled

    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

    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))
    

    Output

    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.

提交回复
热议问题