han*_*s-t 6 python instance-variables
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)))
Run Code Online (Sandbox Code Playgroud)
我的电脑的结果:
访问本地属性:0.686281020000024
访问实例属性:3.7962001440000677
为什么会这样?此外,在使用之前本地化实例变量是一个好习惯吗?
mgi*_*son 10
每次python查找变量时,都需要支付一些费用(LOAD_FAST
操作代码).每次在现有对象上查找属性时,都需要支付更多费用(LOAD_ATTR
操作代码).例如
>>> 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
>>>
Run Code Online (Sandbox Code Playgroud)
即使你不知道如何阅读python反汇编字节码,你也可以看到有更多东西要做,而f2
不是f1
.
另请注意,并非所有操作码都相同. LOAD_FAST
基本上是本地范围内的数组查找(因此顾名思义,它是快速的). LOAD_ATTR
(另一方面)(因为它转换为函数调用(__getattribute__
)(通常)执行字典查找时稍微慢一点.
至于什么是"最佳实践",做最简单的事情.我认为使用它是非常习惯的,self
除非你通过避免它来证明有显着的性能提升,但我不认为这是一个很难的规则.
因为只需使用单字节代码步骤访问局部变量LOAD_FAST
,另一方面self.x
需要首先查找self
使用LOAD_FAST
然后访问x
它,这也很复杂,因为Python必须首先检查它是数据描述符还是仅仅是简单的实例属性并在此基础上获取其值.
通常,在处理CPython中的方法时缓存这种重复调用是个好主意,因为否则每次创建新的绑定对象时都是如此.我几乎没有看到过缓存普通属性以获得一些性能优势的情况.像PyPy和Pyston这样的其他实现有自己的方法来加速属性查找.从数据模型页面:
请注意,每次从类或实例检索属性时,都会发生从函数对象到(未绑定或绑定)方法对象的转换.在某些情况下,卓有成效的优化是 将属性分配给局部变量并调用该局部变量.
这方面的一个例子是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
Run Code Online (Sandbox Code Playgroud)
Python 3.7将有两个新的字节代码来加速方法加载和调用.
添加了两个新的操作码:
LOAD_METHOD
并CALL_METHOD
避免实例化方法调用的绑定方法对象,这会导致方法调用速度提高20%.(由Yury Selivanov和INADA Naoki 供稿于bpo-26110.)