为什么 yield 生成的生成器比 xrange 生成的生成器快?
Why is a generator produced by yield faster than a generator produced by xrange?
我正在研究 Python 生成器并决定 运行 做一个小实验。
TOTAL = 100000000
def my_sequence():
i = 0
while i < TOTAL:
yield i
i += 1
def my_list():
return range(TOTAL)
def my_xrange():
return xrange(TOTAL)
内存使用情况(使用psutil获取进程RSS内存)和所用时间(使用time.time())在运行对每个方法进行多次计算并取平均值后显示如下:
sequence_of_values = my_sequence() # Memory usage: 6782976B Time taken: 9.53674e-07 s
sequence_of_values2 = my_xrange() # Memory usage: 6774784B Time taken: 2.14576e-06 s
list_of_values = my_list() # Memory usage: 3266207744B Time taken: 1.80253s
我注意到使用 xrange 生成生成器的速度始终(稍微)慢于使用 yield 生成的生成器。为什么?
我要在这个答案的开头说,这种规模的时间可能很难准确测量(可能最好使用 timeit
),而且这些优化几乎永远不会您实际程序的 运行 时间有任何差异 ...
好的,现在免责声明完成了...
您需要注意的第一件事是,您只是在计算 generator/xrange 对象的构造时间——您 不是 计算它需要多长时间实际迭代值1。在某些情况下创建生成器可能比创建 xrange 对象更快的原因有几个...
- 对于生成器的情况,您只是在创建一个生成器 -- 生成器中的代码实际上没有得到 运行。这相当于大约 1 个函数调用。
- 对于
xrange
情况,您正在调用函数 和 然后您必须查找全局名称 xrange
,全局 TOTAL
然后你需要调用那个内置函数——所以在这种情况下 有 更多的事情正在执行。
至于内存——在这两种惰性方法中,使用的内存将由 python 运行时间支配——而不是生成器对象的大小。内存使用受脚本明显影响的唯一情况是构建包含 1 亿个项目的列表。
另请注意,我实际上无法在我的系统上始终如一地确认您的结果...使用 timeit
,我实际上得到 my_xrange
有时 2 构建速度更快(约 30%)。
将以下内容添加到脚本底部:
from timeit import timeit
print timeit('my_xrange()', setup='from __main__ import my_xrange')
print timeit('my_sequence()', setup='from __main__ import my_sequence')
我的结果是(对于 OS-X El-Capitan 上的 CPython
):
0.227491140366
0.356791973114
然而,pypy
似乎有利于生成器构造(我先用 my_xrange
和 my_sequence
试过,得到了相当一致的结果,虽然第一个到 运行 似乎有点不利——可能是由于 JIT 预热时间或其他原因):
0.00285911560059
0.00137305259705
1在这里,我 期望 xrange
有优势——但同样,在你 timeit
之前没有什么是真的,然后只有当时间差异很大并且只有在你进行计时的计算机上它才是真的。
2见开头免责声明:-P
正如我在上面的评论中提到的,使用生成器函数和 xrange,您实际上并不是在创建序列,而只是在创建对象。 @mgilson 的回答涵盖了与创建 它们相关的调用。
至于实际和他们一起做点什么:
>>> TOTAL = 100000
>>> # your functions here
...
>>> import timeit
>>> timeit.timeit("list(my_seq())", setup="from __main__ import my_seq", number=1000)
9.783777457339898
>>> timeit.timeit("list(my_xrange())", setup="from __main__ import my_xrange", number=1000)
1.2652621698083024
>>> timeit.timeit("list(my_list())", setup="from __main__ import my_list", number=1000)
2.666709824464867
>>> timeit.timeit("my_list()", setup="from __main__ import my_list", number=1000)
1.2324339537661615
你会看到我正在从每个中创建一个 list
所以我正在处理序列。
生成器函数的时间几乎是 xrange
的 10 倍。
list(my_list)
是多余的,因为 my_list
已经 returns 由 range
生成的列表,所以我又做了一次而没有调用list()
.
range
几乎与 xrange
相同,但那是因为我减少了 TOTAL。最大的区别在于 range
会消耗更多内存,因为它首先创建整个列表,因此仅在该部分 上花费 更长的时间。从 xrange = range 有效地创建一个列表。所以最终使用的内存是相同的,因为我只是在 xrange 之外创建一个列表,所以很难看出这个小案例中的区别。
我正在研究 Python 生成器并决定 运行 做一个小实验。
TOTAL = 100000000
def my_sequence():
i = 0
while i < TOTAL:
yield i
i += 1
def my_list():
return range(TOTAL)
def my_xrange():
return xrange(TOTAL)
内存使用情况(使用psutil获取进程RSS内存)和所用时间(使用time.time())在运行对每个方法进行多次计算并取平均值后显示如下:
sequence_of_values = my_sequence() # Memory usage: 6782976B Time taken: 9.53674e-07 s
sequence_of_values2 = my_xrange() # Memory usage: 6774784B Time taken: 2.14576e-06 s
list_of_values = my_list() # Memory usage: 3266207744B Time taken: 1.80253s
我注意到使用 xrange 生成生成器的速度始终(稍微)慢于使用 yield 生成的生成器。为什么?
我要在这个答案的开头说,这种规模的时间可能很难准确测量(可能最好使用 timeit
),而且这些优化几乎永远不会您实际程序的 运行 时间有任何差异 ...
好的,现在免责声明完成了...
您需要注意的第一件事是,您只是在计算 generator/xrange 对象的构造时间——您 不是 计算它需要多长时间实际迭代值1。在某些情况下创建生成器可能比创建 xrange 对象更快的原因有几个...
- 对于生成器的情况,您只是在创建一个生成器 -- 生成器中的代码实际上没有得到 运行。这相当于大约 1 个函数调用。
- 对于
xrange
情况,您正在调用函数 和 然后您必须查找全局名称xrange
,全局TOTAL
然后你需要调用那个内置函数——所以在这种情况下 有 更多的事情正在执行。
至于内存——在这两种惰性方法中,使用的内存将由 python 运行时间支配——而不是生成器对象的大小。内存使用受脚本明显影响的唯一情况是构建包含 1 亿个项目的列表。
另请注意,我实际上无法在我的系统上始终如一地确认您的结果...使用 timeit
,我实际上得到 my_xrange
有时 2 构建速度更快(约 30%)。
将以下内容添加到脚本底部:
from timeit import timeit
print timeit('my_xrange()', setup='from __main__ import my_xrange')
print timeit('my_sequence()', setup='from __main__ import my_sequence')
我的结果是(对于 OS-X El-Capitan 上的 CPython
):
0.227491140366
0.356791973114
然而,pypy
似乎有利于生成器构造(我先用 my_xrange
和 my_sequence
试过,得到了相当一致的结果,虽然第一个到 运行 似乎有点不利——可能是由于 JIT 预热时间或其他原因):
0.00285911560059
0.00137305259705
1在这里,我 期望 xrange
有优势——但同样,在你 timeit
之前没有什么是真的,然后只有当时间差异很大并且只有在你进行计时的计算机上它才是真的。
2见开头免责声明:-P
正如我在上面的评论中提到的,使用生成器函数和 xrange,您实际上并不是在创建序列,而只是在创建对象。 @mgilson 的回答涵盖了与创建 它们相关的调用。
至于实际和他们一起做点什么:
>>> TOTAL = 100000
>>> # your functions here
...
>>> import timeit
>>> timeit.timeit("list(my_seq())", setup="from __main__ import my_seq", number=1000)
9.783777457339898
>>> timeit.timeit("list(my_xrange())", setup="from __main__ import my_xrange", number=1000)
1.2652621698083024
>>> timeit.timeit("list(my_list())", setup="from __main__ import my_list", number=1000)
2.666709824464867
>>> timeit.timeit("my_list()", setup="from __main__ import my_list", number=1000)
1.2324339537661615
你会看到我正在从每个中创建一个
list
所以我正在处理序列。生成器函数的时间几乎是
xrange
的 10 倍。list(my_list)
是多余的,因为my_list
已经 returns 由range
生成的列表,所以我又做了一次而没有调用list()
.range
几乎与xrange
相同,但那是因为我减少了 TOTAL。最大的区别在于range
会消耗更多内存,因为它首先创建整个列表,因此仅在该部分 上花费 更长的时间。从 xrange = range 有效地创建一个列表。所以最终使用的内存是相同的,因为我只是在 xrange 之外创建一个列表,所以很难看出这个小案例中的区别。