Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/326.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何使Python生成器尽可能快?_Python_Python 3.x_Generator - Fatal编程技术网

如何使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 代码可以找到 对我来说,这与

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

  • 存储在类实例中的所有状态
  • 全局存储的所有状态
  • 所有状态都存储在本地
  • 并使用Python 3.4.3获得以下结果:

    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中,局部变量实际上没有命名;相反,它们被一个数字引用,并通过
      LOAD\u FAST
      opcode访问

    • 使用
      LOAD\u Global
      opcode访问全局变量。它们总是按名称引用,因此每次访问都需要实际的字典查找

    • 实例属性访问速度最慢,因为
      self.foobar
      首先需要使用
      LOAD\u FAST
      加载对
      self
      的引用,然后使用
      LOAD\u ATTR
      在引用的对象上查找
      foobar
      ,这是一个字典查找。此外,如果属性位于实例本身,则这是正常的,但如果属性设置在上,则属性查找速度会变慢。您还在实例上设置值,这将更加缓慢,因为现在它需要在加载的实例上执行
      STORE\u ATTR
      。更复杂的是,还需要查询实例的类-如果该类恰好具有同名的属性描述符,则它可以改变读取和设置属性的行为

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

    为了演示这些差异,考虑为这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必须先检查类上是否有数据描述符,然后再检查实例是否有属性)。