import timeit
class Hello():
def __init__(self):
self.x = 5
def get_local_attr(self):
x = self.x
# 10x10
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
x;x;x;x;x;x;x;x;x;x;
def get_inst_attr(self):
# 10x10
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;self.x;
if __name__ == '__main__':
obj = Hello()
print('Accessing Local Attribute:', min(timeit.Timer(obj.get_local_attr)
.repeat(repeat=5)))
print('Accessing Instance Attribute:', min(timeit.Timer(obj.get_inst_attr)
.repeat(repeat=5)))
我的电脑的结果:
访问本地属性:0.686281020000024
访问实例属性:3.7962001440000677
为什么会这样?此外,在使用之前本地化实例变量是一个好习惯吗?
答案 0 :(得分:10)
每次python查找变量时,都会支付一点(LOAD_FAST
操作码)。每次在现有对象上查找属性时,都需要多付一些(LOAD_ATTR
操作码)。 e.g。
>>> def f1(self):
... x = self.x
... x
...
>>> def f2(self):
... self.x
... self.x
...
>>> dis.dis(f1)
2 0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (x)
6 STORE_FAST 1 (x)
3 9 LOAD_FAST 1 (x)
12 POP_TOP
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
>>> dis.dis(f2)
2 0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (x)
6 POP_TOP
3 7 LOAD_FAST 0 (self)
10 LOAD_ATTR 0 (x)
13 POP_TOP
14 LOAD_CONST 0 (None)
17 RETURN_VALUE
>>>
即使您不知道如何阅读python反汇编字节码,您也可以看到f2
的内容比f1
更多。
另外,请注意并非所有操作码都相同。 LOAD_FAST
基本上是本地范围内的数组查找(因此顾名思义,它是快速的)。另一方面,LOAD_ATTR
稍微慢一点,因为它转换为(通常)进行字典查找的函数调用(__getattribute__
)。
就什么是“最佳实践”而言,做最简单的事情。我认为使用self
是非常习惯的,除非你通过避免它来证明有明显的性能提升,但我不认为这是一个很难的规则。
答案 1 :(得分:6)
由于只使用单字节代码步骤LOAD_FAST
访问局部变量,另一方面self.x
需要首先使用self
查找LOAD_FAST
然后访问x
就此而言,这也很复杂,因为Python必须首先检查它是数据描述符还是仅仅是简单的实例属性,然后根据它来获取它的值。
通常在CPython中处理方法时缓存这种重复调用是个好主意,因为否则每次创建新的绑定对象时都是如此。我很难看到普通属性被缓存以获得一些性能优势的情况。像PyPy和Pyston这样的其他实现有自己的方法来加速属性查找。来自data model页面:
某些例子是请注意,从函数对象转换为(未绑定或 每次从中检索属性时都会发生方法对象 类或实例。在某些情况下,卓有成效的优化是 将属性分配给局部变量并调用该局部变量。
list.append
(另请参阅:https://hg.python.org/cpython/file/f7fd2776e80d/Lib/heapq.py#l372_),例如,如果您要填充包含大量项目的列表,并且不能对某些项目使用列表理解原因然后缓存list.append
提供了轻微的加速:
>>> %%timeit
lst = []
for _ in xrange(10**6):
lst.append(_)
...
10 loops, best of 3: 47 ms per loop
>>> %%timeit
lst = [];append=lst.append
for _ in xrange(10**6):
append(_)
...
10 loops, best of 3: 31.3 ms per loop
Python 3.7将有two new byte codes来加速方法加载和调用。
添加了两个新的操作码:
LOAD_METHOD
和CALL_METHOD
以避免 方法调用的绑定方法对象的实例化,结果 方法调用速度最快可达20%。 (供稿人:Yury Selivanov 和{INNA Naoki在bpo-26110。)
答案 2 :(得分:1)