【发布时间】:2021-02-02 06:44:29
【问题描述】:
我用三个不同的 CPython 版本测试了list(x for x in a)。在a = [0] 上比a = [] 上快得多:
3.9.0 64-bit 3.9.0 32-bit 3.7.8 64-bit
a = [] a = [0] a = [] a = [0] a = [] a = [0]
465 ns 412 ns 543 ns 515 ns 513 ns 457 ns
450 ns 406 ns 544 ns 515 ns 506 ns 491 ns
456 ns 408 ns 551 ns 513 ns 515 ns 487 ns
455 ns 413 ns 548 ns 516 ns 513 ns 491 ns
452 ns 404 ns 549 ns 511 ns 508 ns 486 ns
使用tuple 而不是list,这是预期的相反方式:
3.9.0 64-bit 3.9.0 32-bit 3.7.8 64-bit
a = [] a = [0] a = [] a = [0] a = [] a = [0]
354 ns 405 ns 467 ns 514 ns 421 ns 465 ns
364 ns 407 ns 467 ns 527 ns 425 ns 464 ns
353 ns 399 ns 490 ns 549 ns 419 ns 465 ns
352 ns 400 ns 500 ns 556 ns 414 ns 474 ns
354 ns 405 ns 494 ns 560 ns 420 ns 474 ns
那么,当list(和底层生成器迭代器)必须做更多事情时,为什么它会更快?
在 Windows 10 Pro 2004 64 位上测试。
基准代码:
from timeit import repeat
setups = 'a = []', 'a = [0]'
number = 10**6
print(*setups, sep=' ')
for _ in range(5):
for setup in setups:
t = min(repeat('list(x for x in a)', setup, number=number)) / number
print('%d ns' % (t * 1e9), end=' ')
print()
字节大小,表明它不会为输入 [] 过度分配,而是为输入 [0] 过度分配:
>>> [].__sizeof__()
40
>>> list(x for x in []).__sizeof__()
40
>>> [0].__sizeof__()
48
>>> list(x for x in [0]).__sizeof__()
72
【问题讨论】:
-
不知道是不是和预分配有关。 IIRC,空列表生成一个列表对象,该对象具有为未来元素分配的默认空间量。对于非空列表,
list将仅分配保存迭代器元素所需的数量。元组,因为它们不能增长,总是只分配足够的空间来容纳来自迭代器的内容。 -
@chepner 你指的是哪个列表?原来的
a还是创建的浅拷贝?a是在设置过程中创建的,list(...)和tuple(...)都无法提前知道结果的大小。刚试过,(x for x in []).__length_hint__()报错(不像iter([]).__length_hint__(),返回0)。 -
我无法重现您的结果(Python 3.7 64 位/Win 10 Pro)。有时
[]更快,有时[0] -
@OcasoProtal 很奇怪,我看到
[]在 Python 3.7 64 位 Win10 Pro 上花费的时间是[0]的两倍左右 -
@HeapOverflow 我也无法重现您的结果;作为参考,我获得了:
[]: 463 ns ± 2.69 ns per loop (mean ± std. dev. of 15 runs, 10000000 loops each)和[0]: 471 ns ± 3.4 ns per loop (mean ± std. dev. of 15 runs, 10000000 loops each)。您还应该检查 std.dev。以确保您的结果不会有太大差异。另请提供有关您如何获得这些 Python 发行版的详细信息。
标签: python performance cpython python-internals