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
为什么会出现这种情况?此外,在使用实例变量之前对其进行本地化是一个好习惯吗?
每次 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
>>>
即使你不知道如何阅读 python 反汇编字节码,你也可以看到为
f2
所做的事情比为 f1
做的更多。
LOAD_FAST
基本上是本地范围内的数组查找(因此顾名思义,它是快速的)。 LOAD_ATTR
(另一方面)有点慢,因为它转换为(通常)进行字典查找的函数调用(__getattribute__
)。
就什么是“最佳实践”而言,做最简单的事情。 我认为使用
self
是很习惯的,除非你证明避免它可以显着提高性能,但我不认为这是一个硬性规则。
LOAD_FAST
,另一方面,self.x
将需要首先使用self
查找LOAD_FAST
,然后访问它的x
,也是如此很复杂,因为 Python 必须首先检查它是数据描述符还是简单的实例属性,然后基于此来获取它的值。
通常,在 CPython 中处理 methods 时,缓存如此频繁重复的调用是个好主意,因为否则每次都会创建一个新的绑定对象。我几乎没有见过缓存普通属性以获得一些性能优势的情况。其他实现如 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
Python 3.7 将有两个新的字节码以加快方法加载和调用。
添加了两个新的操作码:
和LOAD_METHOD
以避免 用于方法调用的绑定方法对象的实例化,其结果 方法调用速度加快 20%。 (尤里·塞利瓦诺夫供稿) 和 INADA Naoki 在 bpo-26110。)
CALL_METHOD