如何使Python生成器尽可能快?
对于编写事件驱动的模拟器,我依赖于它,它大量使用Python生成器。我试图了解如何使生成器尽可能快,即最小化状态保存/恢复开销。我尝试了三种选择如何使Python生成器尽可能快?,python,python-3.x,generator,Python,Python 3.x,Generator,对于编写事件驱动的模拟器,我依赖于它,它大量使用Python生成器。我试图了解如何使生成器尽可能快,即最小化状态保存/恢复开销。我尝试了三种选择 存储在类实例中的所有状态 全局存储的所有状态 所有状态都存储在本地 并使用Python 3.4.3获得以下结果: class_generator 20.851247710175812 global_generator 12.802394330501556 local_generator 9.067587919533253 代码可以找到 对我来说,这与
class_generator 20.851247710175812
global_generator 12.802394330501556
local_generator 9.067587919533253
代码可以找到
对我来说,这与直觉相反:在类实例中存储所有状态意味着只需保存/还原self
,而全局存储所有状态应确保零保存/还原开销
有人知道为什么类生成器和全局生成器比本地生成器慢吗?生成器需要保存的唯一状态是对堆栈帧的引用,因此保存和恢复状态所需的时间完全相同,无论涉及多少状态,也不管您将数据放在何处
您在计时中看到的差异完全取决于Python访问值的速度:局部变量访问非常快,全局变量访问需要在全局字典中查找值,因此速度较慢,类成员访问需要访问局部变量“self”,然后对该值执行至少一次字典查找(同时,对类生成器的调用必须转换为具有单个参数的调用,该参数本身比没有参数的其他调用慢).当产生收益时,生成器实际上保留了实际的调用帧。无论您有1个或100个局部变量,它都不会真正影响性能 性能差异实际上来自于Python的使用方式(这里我使用的是CPython a.k.a.,您可以从中下载,或者作为
/usr/bin/Python
安装在您的操作系统上,但由于基本相同的原因,大多数实现都具有类似的性能特征)在不同类型的变量查找中表现:
- 在Python中,局部变量实际上没有命名;相反,它们被一个数字引用,并通过
opcode访问LOAD\u FAST
- 使用
opcode访问全局变量。它们总是按名称引用,因此每次访问都需要实际的字典查找LOAD\u Global
- 实例属性访问速度最慢,因为
首先需要使用self.foobar
加载对LOAD\u FAST
的引用,然后使用self
在引用的对象上查找LOAD\u ATTR
,这是一个字典查找。此外,如果属性位于实例本身,则这是正常的,但如果属性设置在类上,则属性查找速度会变慢。您还在实例上设置值,这将更加缓慢,因为现在它需要在加载的实例上执行foobar
。更复杂的是,还需要查询实例的类-如果该类恰好具有同名的属性描述符,则它可以改变读取和设置属性的行为STORE\u ATTR
为了演示这些差异,考虑为这3个变量访问生成的代码<代码> A<代码> >代码> B<代码> >代码>
a = 42
class Foo(object):
def __init__(self):
self.c = 42
def foo(self):
b = 42
yield a
yield b
yield self.c
print(list(Foo().foo())) # prints [42, 42, 42]
foo
方法的反汇编的相关部分是:
8 6 LOAD_GLOBAL 0 (a)
9 YIELD_VALUE
10 POP_TOP
9 11 LOAD_FAST 1 (b)
14 YIELD_VALUE
15 POP_TOP
10 16 LOAD_FAST 0 (self)
19 LOAD_ATTR 1 (c)
22 YIELD_VALUE
23 POP_TOP
LOAD\u GLOBAL
和LOAD\u ATTR
的操作数分别引用名称a
和c
;这些数字是表上的索引。LOAD\u FAST
的操作数是局部变量表中的局部变量数。我不相信调用类生成器与迭代生成器相比会有任何可观的开销。该调用只需要执行一次(当生成器实例化时),对于所有三个生成器都是一样的,调用其\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu方法。类生成器速度最慢,因为实例上的属性查找相对复杂(Python必须先检查类上是否有数据描述符,然后再检查实例是否有属性)。