timeit偶尔返回负值,可能是bug

1 投票
1 回答
2934 浏览
提问于 2025-04-18 14:54

我在做Project Euler的问题时,决定加个计时功能,于是用timeit来测量main()的运行时间,代码如下(为了方便,我在main()里存了个全局变量RESULT

t = timeit.timeit(main, 'gc.enable()', number=1)
print("# Euler", PROBLEM, ".py RESULT: ", RESULT))

这样做效果不错。但是,有些运行得太快了,我想试试别的方法。

t = timeit.timeit(main, 'gc.enable()', number=1)
if (t < 0.001):
    t2 = timeit.timeit(main, 'gc.enable()', number=1000)

有时候这样做是有效的。不过如果我反复运行,有时会得到负值的t2。以Euler #2为例,我连续运行5次得到的结果是这样的。

# Euler2.py RESULT: 4613732  3.17869758716347e-05 seconds
repeats timing  -3.7966224778973e-05 sec per call

# Euler2.py RESULT: 4613732  3.1558464885145785e-05 seconds
repeats timing  2.4836235955056177e-05 sec per call

# Euler2.py RESULT: 4613732  3.131149340411519e-05 seconds
repeats timing  -3.5684903805855466e-05 sec per call

# Euler2.py RESULT: 4613732  3.177450256451194e-05 seconds
repeats timing  2.4558941864410162e-05 sec per call

# Euler2.py RESULT: 4613732  3.158939868681022e-05 seconds
repeats timing  2.4268726425449536e-05 sec per call

现在如果我把重复次数改成100,000次或更多,就完全看不到负的t2值了,每次调用的时间稳定在大约2.4e-5秒。

如果我重复10,000次,情况又有些不同。t2值始终是正的,但数值波动很大。10次运行的结果是

repeats timing  2.4194581894745244e-05 sec per call
repeats timing  1.8200670315524775e-05 sec per call
repeats timing  2.4408832248987168e-05 sec per call
repeats timing  2.4378118077314547e-05 sec per call
repeats timing  1.8361976570139902e-05 sec per call
repeats timing  1.8055080028738498e-05 sec per call
repeats timing  1.8102133534236732e-05 sec per call
repeats timing  2.4485323058654477e-05 sec per call
repeats timing  3.118363087991698e-05 sec per call
repeats timing  1.803846408685413e-05 sec per call

最后,我把重复次数设为1000,并去掉了最开始的(repeat=1)timeit。结果和之前差不多,有些负值,波动也很大。

我用Python 2.7重复了这个过程,结果类似——其他的都是3.4版本。

在我看来,这看起来像是timeit功能的一个bug,因为总时间和系统定时器的中断时间差不多,但我觉得可能是我漏掉了什么。

补充一下

我还知道其他的计时函数,比如perf_counter()。

我特别问timeit()是因为我觉得它是个简单易用的高精度计时器,可以在新旧版本的Python中使用,如果它是可靠的,我希望继续使用。

补充一下

根据得到的回答,我把我的计时代码改成了基于perf_counter(),结果发现有时也会得到负值。所以,如果在旧的Windows系统上使用,小的计时增量根本不可靠。这就是我想知道的。让我觉得问题出在Python的原因是,对于非常小的计时,数值似乎是准确的。应该猜到是Windows和设备驱动的组合问题。

1 个回答

5

在Windows系统上,timeit默认使用time.clock来获取时间,而这个函数又是通过Windows的APIQueryPerformanceCounter来工作的。根据Windows的版本和机器的性能,QueryPerformanceCounter会使用处理器的时间戳计数器(TSC)作为计时器。不过,一些老旧的多处理器机器在不同处理器之间无法保持TSC的同步,导致它们向Windows报告的时间不准确,或者在尝试同步时出现了错误。这就导致了QueryPerformanceCounter返回的结果在不同处理器上执行时,看起来像是跳来跳去的。

微软在MSDN上对这个问题有详细的描述:http://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx

AMD为Windows XP系统发布了一个修复这个问题的工具,叫做AMD Dual Core Optimizer

撰写回答