`yield from` 生成器 vs `yield from` 列表性能

pt1*_*lol 6 python yield list generator coroutine

Python 3.6.8 (default, Oct  7 2019, 12:59:55) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.9.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: def yield_from_generator(): 
   ...:     yield from (i for i in range(10000)) 
   ...:                                                                                                                                    

In [2]: def yield_from_list(): 
   ...:     yield from [i for i in range(10000)] 
   ...:                                                                                                                                    

In [3]: import timeit                                                                                                                      

In [4]: timeit.timeit(lambda: list(yield_from_generator()), number=10000)                                                                  
Out[4]: 5.3820097140014695

In [5]: timeit.timeit(lambda: list(yield_from_list()), number=10000)                                                                       
Out[5]: 4.333915593000711
Run Code Online (Sandbox Code Playgroud)

我多次运行yield from生成器并yield from列出。列表版本总是提供更好的性能,而我的直觉告诉我相反的结论 - 制作列表需要在启动时分配内存。为什么我们可以注意到这种性能差异?

Sam*_*son 3

简而言之,表面语法使它们看起来比实际情况更相似

\n\n

我将更详细地分解一系列函数(该dis模块对此很有帮助),我将把它们分成设置成本和每个产生值的成本。我们从以下开始:

\n\n
def yield_from_generator():\n    yield from (i for i in range(10000))\n
Run Code Online (Sandbox Code Playgroud)\n\n

费用是:

\n\n
    \n
  • 设置:创建范围对象并调用嵌入的生成器表达式
  • \n
  • per-yield:yield from the genexpr,它还会调用迭代器next上的a range。请注意,这里有两个上下文切换
  • \n
\n\n

接下来我们看:

\n\n
def yield_from_list():\n    yield from [i for i in range(10000)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

成本是:

\n\n
    \n
  • 设置:创建一个新列表并使用列表理解填充它。这使用特殊的list操作码,所以会很快
  • \n
  • per-yield:只是恢复 的list迭代器,所以速度很快
  • \n
\n\n

接下来我们看一个类似的函数:

\n\n
def yield_from_list2():\n    yield from list(i for i in range(10000))\n
Run Code Online (Sandbox Code Playgroud)\n\n

这不使用特殊的列表操作码,并且具有生成器的双重嵌套,因此又很慢。成本是:

\n\n
    \n
  • 设置:创建一个新的生成器表达式并将其传递给列表构造函数,这将迭代遍历范围对象的生成器表达式
  • \n
  • per-yield:使用 的list迭代器,所以又快了
  • \n
\n\n

最后是一个快速版本,只是强调yield from

\n\n
def yield_from_generator2():\n    yield from range(10000)\n
Run Code Online (Sandbox Code Playgroud)\n\n

成本是:

\n\n
    \n
  • 设置:创建一个range对象
  • \n
  • per-yield:range直接恢复迭代器
  • \n
\n\n

我的笔记本电脑上所有这些的时间是:

\n\n
yield_from_generator  639 \xc2\xb5s\nyield_from_list       536 \xc2\xb5s\nyield_from_list2      689 \xc2\xb5s\nyield_from_generator2 354 \xc2\xb5s\n
Run Code Online (Sandbox Code Playgroud)\n\n

希望现在更清楚了。另一个版本是:

\n\n
def yield_from_list3():\n    yield from list(range(10000))\n
Run Code Online (Sandbox Code Playgroud)\n\n

运行在 401 \xc2\xb5s 但希望更明显为什么它位于中间,性能方面

\n