【发布时间】:2019-09-13 21:58:07
【问题描述】:
根据this answer 的说法,在许多情况下,列表的性能优于生成器,例如与str.join 一起使用时(因为算法需要传递数据两次)。
在以下示例中,使用 列表推导 似乎比使用相应的生成器表达式产生更好的性能,尽管直观地,列表推导伴随着分配和复制到生成器回避的额外内存的开销。
In [1]: l = list(range(2_000_000))
In [2]: %timeit l[:] = [i*3 for i in range(len(l))]
190 ms ± 4.65 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [3]: %timeit l[:] = (i*3 for i in range(len(l)))
261 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [4]: %timeit l[::2] = [i*3 for i in range(len(l)//2)]
97.1 ms ± 2.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [5]: %timeit l[::2] = (i*3 for i in range(len(l)//2))
129 ms ± 2.21 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [6]: %timeit l[:len(l)//2] = [i*3 for i in range(len(l)//2)]
92.6 ms ± 2.34 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [7]: %timeit l[:len(l)//2] = (i*3 for i in range(len(l)//2))
118 ms ± 2.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
为什么列表推导在这些情况下会产生更好的性能?
【问题讨论】:
-
可能
l[:]是一个切片,所以要让类型匹配,生成器必须在后台转换为列表 -
@C.Nivs
l[:] = ...等价于l.__setitem__(slice(None), ...)但是为什么生成器需要转成列表呢? -
来自Python language reference:
If the target is a slicing: The primary expression in the reference is evaluated. It should yield a mutable sequence object (such as a list). The assigned object should be a sequence object of the same type.因此,必须将生成器强制转换为list类型 -
顺便补充一句,迭代生成器很慢。尝试对
for x in [i for i in range(10_000)]: pass和for x in (i for i in range(10_000)): pass计时,您会发现,即使您必须使用列表解析版本进行两次遍历,列表解析的迭代仍然总体上更快。直到我们处理大约 1_000_000 个项目,我才开始看到生成器表达式获胜,即使那样它也只是稍微快一点...... -
@juanpa.arrivillaga 好的,但是虽然我为了示例而使用了生成器表达式,但想象一下我从其他地方获取生成器。乍一看,首先用尽生成器,然后将其复制到原始列表中似乎很浪费——而不是立即覆盖列表中的项目(对于非扩展切片分配)。我知道,因为原始列表的大小可能会在该操作期间发生变化,所以从一开始就知道新的大小是有利的(尽管我可以想象一个动态调整大小的算法 - 如果有必要的话)。
标签: python python-3.x list list-comprehension generator-expression