为什么在Python中,访问本地变量比访问类成员变量更快?

19

尝试解决一个更复杂的问题时,我比较了访问本地变量和成员变量的速度。

这是一个测试程序:

#!/usr/bin/env python

MAX=40000000

class StressTestMember(object):
    def __init__(self):
        self.m = 0

    def do_work(self):
        self.m += 1
        self.m *= 2

class StressTestLocal(object):
    def __init__(self):
        pass

    def do_work(self):
        m = 0
        m += 1
        m *= 2

# LOCAL access test
for i in range(MAX):
    StressTestLocal().do_work()

# MEMBER access test
for i in range(MAX):
    StressTestMember().do_work()
我知道在每次迭代中实例化StressTestMemberStressTestLocal可能看起来不是一个好主意,但在模拟的程序中,这其实是有意义的,因为它们基本上是 Active Records。
经过简单的基准测试:
  • 本地访问测试: 0m22.836
  • 成员访问测试: 0m32.648s
本地版本是类的一部分,速度快大约33%。为什么?
2个回答

32

self.m += 1 的意思是您需要查找名为 self 的本地变量,然后找到名为 m 的属性。

当然,如果只需查找本地变量,则无需进行额外步骤会更快。

查看底层发生了什么可能很有用:

>>> import dis
>>> dis.dis(StressTestLocal.do_work)
 18           0 LOAD_CONST               1 (0)
              3 STORE_FAST               1 (m)

 19           6 LOAD_FAST                1 (m)
              9 LOAD_CONST               2 (1)
             12 INPLACE_ADD         
             13 STORE_FAST               1 (m)

 20          16 LOAD_FAST                1 (m)
             19 LOAD_CONST               3 (2)
             22 INPLACE_MULTIPLY    
             23 STORE_FAST               1 (m)
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE        
>>> dis.dis(StressTestMember.do_work)
 10           0 LOAD_FAST                0 (self)
              3 DUP_TOP             
              4 LOAD_ATTR                0 (m)
              7 LOAD_CONST               1 (1)
             10 INPLACE_ADD         
             11 ROT_TWO             
             12 STORE_ATTR               0 (m)

 11          15 LOAD_FAST                0 (self)
             18 DUP_TOP             
             19 LOAD_ATTR                0 (m)
             22 LOAD_CONST               2 (2)
             25 INPLACE_MULTIPLY    
             26 ROT_TWO             
             27 STORE_ATTR               0 (m)
             30 LOAD_CONST               0 (None)
             33 RETURN_VALUE        

那么,是否应该明智地在本地作用域中创建一个类变量的新引用呢?例如,m = self.m?这在这个测试中不会有任何区别,但我的 do_work() 版本是一个运行数百万次的循环。 - James S
@JamesS,如果你觉得调整很重要的话,最好测量一下你确切的用例。如果你引用了self.m几次,我会期望将其提取到本地变量中会更快。记得在修改它时将本地变量保存回属性。 - John La Rooy

7

本地名称更快,因为Python进行了一些优化,本地名称不需要字典访问,另一方面,实例属性需要访问对象的__dict__

这也是本地名称比全局名称更快的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接