【发布时间】:2021-12-27 18:04:34
【问题描述】:
我创建了两个重复两个不同值的长列表。在第一个列表中,值交替出现,在第二个列表中,一个值出现在另一个值之前:
a1 = [object(), object()] * 10**6
a2 = a1[::2] + a1[1::2]
然后我迭代它们,对它们什么都不做:
for _ in a1: pass
for _ in a2: pass
两者中哪一个迭代得更快?取决于我如何测量!我用每种计时方法进行了 50 场比赛:
timing method: timeit.timeit
list a1 won 50 times
list a2 won 0 times
timing method: timeit.default_timer
list a1 won 0 times
list a2 won 50 times
为什么这两种计时方法给我的结果完全相反?为什么这两个列表之间存在速度差异?我希望两次更像 25-25,而不是 50-0 和 0-50。
以前类似问题的原因以及我认为他们在这里不负责任的原因:
- branch prediction:我没有任何可以产生影响的分支。
- cache misses:不应该发生,因为我只有两个值(而且我什至没有对它们做任何事情)。
- garbage collection:这里不涉及。
- 无论如何,这些都不能解释计时方法之间的相反速度差异。
我使用的是 Python 3.10,在弱 Windows 笔记本电脑和强 Linux Google Compute Engine 实例上的结果相同。
完整代码:
from timeit import timeit, default_timer
a1 = [object(), object()] * 10**6
a2 = a1[::2] + a1[1::2]
for method in 'timeit', 'default_timer':
wins1 = wins2 = 0
for _ in range(50):
time1 = time2 = float('inf')
for _ in range(5):
if method == 'timeit':
t1 = timeit('for _ in a1: pass', 'from __main__ import a1', number=1)
t2 = timeit('for _ in a2: pass', 'from __main__ import a2', number=1)
else:
start = default_timer();
for _ in a1: pass
end = default_timer()
t1 = end - start
start = default_timer();
for _ in a2: pass
end = default_timer()
t2 = end - start
time1 = min(time1, t1)
time2 = min(time2, t2)
wins1 += time1 < time2
wins2 += time2 < time1
print(f'timing method: timeit.{method}')
print(f' list a1 won {wins1} times')
print(f' list a2 won {wins2} times')
print()
【问题讨论】:
-
好的,在完整阅读了你的问题之后,可能是为了澄清一个没有比另一个快 factor 50,而是在与他们比赛之后互相50次,一个赢了50次,另一个赢了0次。
-
您可以通过选择随机进行测量的顺序来减少系统影响。那么问题可能不是“为什么 x 比 y 快”而是“为什么第一次测量比第二次测量快”
-
@mkrieger1 谢谢,已更改。现在清楚了吗?
-
我想立即指出一件事,以防这不明显 - 您的列表不是交替值,而是对两个对象的引用。您在内存中有两个对象,并且您的两个列表都只包含指向这些相同对象的引用。为什么这些引用的顺序会导致不同的计时器方法测量不同,我不确定,我需要深入研究这两种方法。
-
@JérômeRichard(续)参见this answer 另一个类似问题。这还观察到单个重复值比交替重复的两个值慢,并推测“管道停顿”可能是罪魁祸首。如果这是原因之一,那么我会在这里了解有关 that 的知识,而不仅仅是有关 CPython 的知识。
标签: python list performance iteration