【问题标题】:Does the global keyword inline the global exactly as a local variable declaration in python?global 关键字是否将 global 完全内联为 python 中的局部变量声明?
【发布时间】:2016-07-10 18:04:51
【问题描述】:

这两个完全在性能上是等价的(即生成的代码是否完全等价):

class A(object):
    const = 'abc'

    def lengthy_op(self):
        const = self.const
        for i in xrange(AVOGADRO):
            # do something which involves reading const

和:

const = 'abc'
class A(object):

    def lengthy_op(self):
        global const
        for i in xrange(AVOGADRO):
            # do something which involves reading const

【问题讨论】:

  • 如果您以只读方式使用变量,则不需要全局关键字。我希望点访问会稍微慢一些,但对这些问题的回答总是“衡量它”。无论如何,它不太可能成为性能瓶颈。
  • @Rogalski:我知道这不是必需的 - 我的问题是它是否在本地范围内内联全局(使其与另一个等效)。点访问执行一次,全局查找也执行一次-我的问题是它们之后是否等效-在两种情况下都只使用名称“const”的循环中。这将由可以生成字节码的人立即回答,而不是通过测量,它应该是确定的 - 除非我错过了什么......
  • 我希望全局访问会更慢,因为变量名首先在本地范围的符号表中搜索,并且只有在没有找到任何内容后才在全局中搜索
  • 实际上,global 说明符虽然对正确性来说是不必要的,但可能会提高性能,因为它会绕过本地范围内的任何检查。
  • @TomKarzes:正是我的意思——现在如果我们可以删除那个“可能”......

标签: python performance python-2.7 global globals


【解决方案1】:

不,它们并不完全等价,尽管差别不大。

class A(object):
    const = 'abc'

    def lengthy_op(self):
        const = self.const
        for i in xrange(AVOGADRO):
            # do something which involves reading const

这会创建一个局部变量,因此任何对 const 的访问都将使用 LOAD_FAST 操作码。

const = 'abc'
class A(object):

    def lengthy_op(self):
        # global const
        for i in xrange(AVOGADRO):
            # do something which involves reading const

无论有没有冗余的global const,这都使用LOAD_GLOBAL 来访问全局变量constxrangeAVOGADRO 的值。

在 C 语言中,Python LOAD_GLOBAL 将执行快速字典查找以访问变量(速度很快,因为全局变量位于仅使用字符串键的字典中,并且哈希值是预先计算的)。另一方面,LOAD_FAST 只是访问第一、第二、第三等局部变量,这是一个数组索引操作。

其他版本的 Python(例如 PyPy)可能能够优化访问全局变量,在这种情况下可能根本没有任何区别。

第一个代码(以n=i+const 作为循环体)反汇编为:

>>> dis.dis(A.lengthy_op)
  5           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (const)
              6 STORE_FAST               1 (const)

  6           9 SETUP_LOOP              30 (to 42)
             12 LOAD_GLOBAL              1 (xrange)
             15 LOAD_GLOBAL              2 (AVOGADRO)
             18 CALL_FUNCTION            1
             21 GET_ITER            
        >>   22 FOR_ITER                16 (to 41)
             25 STORE_FAST               2 (i)

  8          28 LOAD_FAST                2 (i)
             31 LOAD_FAST                1 (const)
             34 BINARY_ADD          
             35 STORE_FAST               3 (n)
             38 JUMP_ABSOLUTE           22
        >>   41 POP_BLOCK           
        >>   42 LOAD_CONST               0 (None)
             45 RETURN_VALUE        

而第二块给出:

>>> dis.dis(A.lengthy_op)
  5           0 SETUP_LOOP              30 (to 33)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_GLOBAL              1 (AVOGADRO)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                16 (to 32)
             16 STORE_FAST               1 (i)

  7          19 LOAD_FAST                1 (i)
             22 LOAD_GLOBAL              2 (const)
             25 BINARY_ADD          
             26 STORE_FAST               2 (n)
             29 JUMP_ABSOLUTE           13
        >>   32 POP_BLOCK           
        >>   33 LOAD_CONST               0 (None)
             36 RETURN_VALUE        

Python 不会制作全局的本地副本,因为没有简单的方法可以确保在代码运行时全局值不会改变。任何东西,甚至是另一个线程或调试器,都可以在循环执行时修改该值。

【讨论】:

  • 呵呵——所以添加全局关键字不等于global const; local_const = const; # then use local_const。有什么理由吗?全局声明这样做是否没有意义,否则会破坏某些东西?
  • Python 不知道全局 const 不会发生变化,因此它不能只是复制它,或者至少不能不做 Python 的 C 实现没有做的其他优化.
  • 所以我想知道的底线是,如果全局变量是只读的,是否只会向局部变量添加一个条目 - 编辑:“任何东西,甚至是另一个线程或调试器,都可以修改该值,而循环正在执行” - 非常感谢,这正是我想要的 :)
  • 至于性能,我打赌没有分析,这种差异会对 AVOGADRO 产生几千年的影响:D 为什么每当有人问到性能问题时,人们就会向你抛出库恩的说法,好像你不知道而不是看这个问题让我逃避 - 嘿,伙计们的表现不是禁忌,我们在这里同意成年人;)
  • 实际上,我认为您会发现您的代码会抛出 TypeError: integer argument expected, got floatOverflowError: Python int too large to convert to C long,具体取决于 AVOGADRO 的值。升级到 Python 3 并使用range(),您可能会得到它的工作。
【解决方案2】:

它是更快还是更慢实际上取决于您的范围,范围存储在字典中,字典越小(稍微)访问速度就越快。由于字典是作为哈希集实现的,因此查找性能为 O(1)。

每当您尝试访问变量时,Python 都会按以下顺序遍历范围:

  • 本地。作为当前函数作用域的本地命名空间。
  • 封闭函数局部变量。根据嵌套函数/lambda 的数量,可能会有更多。
  • 全球。全局范围,它只是另一个字典(您可以通过 globals() 访问)
  • 内置插件。适用于所有范围的标准 Python 内置函数,例如 listint 等。

访问函数/类属性的工作方式类似,但涉及:

  • __getattribute__
  • __dict__
  • __getattr__

所有继承的类也是如此。

邓肯完美回答了您的其余问题

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-06-15
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 2016-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多