Python itertools与用于展平和重复的生成器表达式-我们如何解释这些计时结果?
出于对的讨论,我决定尝试一些性能测试。我设置的任务稍微简单一些-给定一个源列表Python itertools与用于展平和重复的生成器表达式-我们如何解释这些计时结果?,python,performance,itertools,generator-expression,Python,Performance,Itertools,Generator Expression,出于对的讨论,我决定尝试一些性能测试。我设置的任务稍微简单一些-给定一个源列表a,我们希望创建一个lazy iterable,它将a的每个元素重复N次: def测试(实施): A、 N=列表('abc'),3 断言列表(实现(A,N))==list('aaabbccc') 我提出了几个实现,并对它们进行了测试: 从itertools导入链,重复,星图 从timeit导入timeit 展平=链从可调 def消耗(iterable): 对于iterable中的uu: 通过 #快速接近 def工具(
a
,我们希望创建一个lazy iterable,它将a的每个元素重复N次:
def测试(实施):
A、 N=列表('abc'),3
断言列表(实现(A,N))==list('aaabbccc')
我提出了几个实现,并对它们进行了测试:
从itertools导入链,重复,星图
从timeit导入timeit
展平=链从可调
def消耗(iterable):
对于iterable中的uu:
通过
#快速接近
def工具(原始,计数):
返回展平(贴图(重复、原始、重复(计数)))
def tools_star(原始,计数):
返回展平(星图(重复,压缩(原始,重复(计数)))
def混合(原始,计数):
返回展平(对原件中的a重复(a,计数)
#慢进
def混合2(原始,计数):
返回值(x代表a原件,x代表重复(a,计数))
def显式(原始,计数):
对于原件:
对于范围内的(计数):
产生
def发生器(原始,计数):
返回值(a为原始值,a为范围内(计数))
def混合3(原始,计数):
原稿中a的返回展平((a表示范围内(计数))值)
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
对于impl in(工具、工具、星型、混合、混合2、显式、生成器、混合3):
对于中的消费(消费,列表):
to_时间=λ:消耗量(impl(列表(范围(1000)),1000))
已用时间=时间(到时间,数字=100)
打印(f'{consumption.\uuuuuu name\uuuuuu}({impl.\uuuuuuu name\uuuuuuuuu}):{已用时间:.2f})
以下是我的机器上计时结果的三个示例:
consume(tools): 1.10
list(tools): 2.96
consume(tools_star): 1.10
list(tools_star): 2.97
consume(mixed): 1.11
list(mixed): 2.91
consume(mixed2): 4.60
list(mixed2): 6.53
consume(explicit): 5.45
list(explicit): 8.09
consume(generator): 5.98
list(generator): 7.62
consume(mixed3): 5.75
list(mixed3): 7.67
consume(tools): 1.10
list(tools): 2.88
consume(tools_star): 1.10
list(tools_star): 2.89
consume(mixed): 1.11
list(mixed): 2.87
consume(mixed2): 4.56
list(mixed2): 6.39
consume(explicit): 5.42
list(explicit): 7.24
consume(generator): 5.91
list(generator): 7.48
consume(mixed3): 5.80
list(mixed3): 7.61
consume(tools): 1.14
list(tools): 2.98
consume(tools_star): 1.10
list(tools_star): 2.90
consume(mixed): 1.11
list(mixed): 2.92
consume(mixed2): 4.76
list(mixed2): 6.49
consume(explicit): 5.69
list(explicit): 7.38
consume(generator): 5.68
list(generator): 7.52
consume(mixed3): 5.75
list(mixed3): 7.86
由此我得出以下结论:
工具提供了巨大的性能提升,但前提是我们同时使用它们来“展平”迭代器(itertools
而不是通过嵌套的itertools.chain.from_iterable
表达式展平),并生成子序列(for
而不是itertools.repeat
)。只使用<代码>重复只提供了一个小的改进,并且只使用<代码>链。从_iterable实际上似乎让事情变得更糟range
- 对于完整的
实现,我们如何迭代输入序列似乎并不重要——无论是使用生成器表达式、使用itertools
还是使用map
。(这并不奇怪,因为这里只发生了O(len(A))操作,而不是O(len(A)*N)。星图的itertools.starmap
方法非常笨拙,肯定不是我推荐的方法,但我将其包括在内,因为原始激励讨论中的代码使用了它。)starmap
- 从iterable创建列表所增加的开销量在不同方法和不同计时运行(请注意两次运行的
结果的差异)-尽管它们对于快速方法似乎更一致。这尤其奇怪,因为我在总结每次测试中创建的多个列表的结果list(explicit)
链接的方式。from_iterable
和repeat
在这里不提供增量性能优势,而是完全相互依赖。列表的构建是怎么回事?在每种情况下增加的开销是不是都是一样的(重复地将相同的元素序列附加到一个空列表中)?它主要归结为花在解释器中的时间量的大O
- 解释器中没有循环允许C函数直接通信。
- 但是嵌套如此多的itertools会增加很小但可以测量的开销。
在下表中I
- 但是嵌套如此多的itertools会增加很小但可以测量的开销。
- 一个循环仅为少数操作码的×1000
- 嵌套循环的数量高达1000000个
- 直接从
屈服比屈服前从repeat
存储短一些操作码。range
在下表中Y
和explicit
实际上是等效的generator
- 嵌套生成器是一种嵌套函数调用-代价高昂。
在下表中G
O(0)+I
0.21
0.09
工具之星
O(0)+I
0.21
0.09
混合的
O(N)
0.20
0.09
混合2
O(N²)
0.54
0.47
明确的
O(N²)+Y
0.64
0.60
发电机
O(N²)+Y
0.64
0.60
工具
O(N²)+G
0.71
0.65
时间变化很可能来自不相关的进程,例如malloc内部或操作系统调度。在我的机器上,输入的是2%的倍数,你的看起来很相似。