如何尽可能快地制作Python生成器?

use*_*136 4 python generator python-3.x

对于编写事件驱动的模拟器,我依赖于simpy,它大量使用Python生成器.我试图了解如何尽可能快地生成生成器,即最小化状态保存/恢复开销.我尝试了三种选择

  1. 所有状态都存储在类实例中
  2. 全局存储全部状态
  3. 所有状态都存储在本地

并使用Python 3.4.3获得以下结果:

class_generator 20.851247710175812
global_generator 12.802394330501556
local_generator 9.067587919533253
Run Code Online (Sandbox Code Playgroud)

代码可以在这里找到.

这对我来说是违反直觉的:在类实例中存储所有状态意味着只self需要保存/恢复,而全局存储所有状态应确保零保存/恢复开销.

有谁知道为什么类生成器和全局生成器比本地生成器慢?

Ant*_*ala 10

产量发生时,生成器实际上保留了实际的调用帧.无论您有1个还是100个局部变量,它都不会真正影响性能.

性能差异实际上来自于Python(这里我使用的是CPython,也就是你从http://www.python.com/下载的那个,或者在你的操作系统上/usr/bin/python,但大多数实现都有类似的性能)由于大多数相同的原因导致的特征表现在不同类型的变量查找上:

  • 在Python中实际上没有命名局部变量; 相反,它们由数字引用,并由LOAD_FAST操作码访问.

  • 使用LOAD_GLOBAL操作码访问全局变量.它们总是被名称引用,因此每次访问都需要实际的字典查找.

  • 实例属性访问是最慢的,因为self.foobar首先需要使用LOAD_FAST加载引用self,然后LOAD_ATTR用于foobar在引用的对象上查找,这是一个字典查找.此外,如果属性在实例本身上,这将是正常的,但如果在上设置,则属性查找将变慢.您还要在实例上设置值,它会更慢,因为现在它需要STORE_ATTR在加载的实例上执行.更复杂的是,实例的也需要被查询 - 如果恰好具有相同名称的属性描述符,那么它可以改变读取和设置属性的行为.

因此,最快的生成器是仅引用局部变量的生成器.Python代码中常见的习惯用法是将全局只读变量的值存储到局部变量中以加快速度.

为了演示这些差异,请考虑为这3个变量访问生成的代码a,b并且self.c:

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]
Run Code Online (Sandbox Code Playgroud)

反汇编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
Run Code Online (Sandbox Code Playgroud)

操作数LOAD_GLOBALLOAD_ATTR名称分别是ac; 数字是桌子上的指数.操作数LOAD_FAST局部变量表表中局部变量的编号.


Dun*_*can 6

生成器需要保存的唯一状态是对堆栈帧的引用,因此无论涉及多少状态以及放置数据的位置,保存和恢复状态都会花费完全相同的时间.

您在时间上看到的差异完全取决于Python可以访问值的速度:本地变量访问速度非常快,全局变量访问需要查找全局字典中的值,因此速度较慢,并且类成员访问需要访问本地变量'self',然后对该值执行至少一次字典查找(同样,对类生成器的调用必须转换为具有单个参数的调用,该参数本身比没有其他调用的调用慢参数).