为什么第一次运行它通常比后续运行慢?



我一直在测试一段代码的一些优化(特别是elif n in [2,3]是否比elif n == 2 or n == 3快(,并注意到一些奇怪的事情。

使用timeit.default_timer我对函数的每个版本进行了多次运行,第一次运行总是比后续运行慢得多(值从大约 0.01 秒开始,一直拖到大约 0.003(。

python 是否在幕后做了一些事情来优化以后运行中的代码?无论如何,这都不是真正的问题,但我有兴趣知道发生了什么(如果有的话(

在参考Python实现CPython上没有这样的一般优化。有各种各样的更具体的事情可能发生,但我们无法说出是什么。


Marcos 的回答表明这是pyc文件创建,但这不是timeit的工作方式,即使您自己调用timeit.default_timer(您不应该 - 您应该使用timeit.timeittimeit.repeat或其他此类机制(。

pyc文件是在导入没有pyc文件或其pyc文件过期的模块时创建的。它们不是为timeit代码段创建的,即使您的定时代码来自导入的模块,典型的timeit使用模式也会在计时开始之前导入模块。

您调用timeit.default_timer而不是让timeit按照其设计的工作方式处理事情,但即便如此,任何pyc文件创建都不太可能在定时代码内发生。

PyPy

是另一种Python实现,使用JIT编译,但你可能知道你是否在PyPy上。

Numba是一个用于加速数值计算的库,它有自己的JIT机制,这也可能导致第一次运行后的加速。在不注意的情况下依赖 Numba 比在不注意的情况下在 PyPy 上运行更容易。

在后续运行中,内存分配可能会发生得更快,具体取决于您使用的类型、它们如何与内存管理系统交互的详细信息,以及malloc的行为方式。例如,在首次运行后,可能会有空闲列表,其中包含更多内存块。

还有其他可能性,但最终,我们无法真正判断发生了什么。

一个主要的考虑因素是,进口的间接费用可能只影响第一轮的时间调用。

请考虑以下示例:

from timeit import timeit
for _ in range(5):
print(timeit('import requests', number=1))

输出将如下所示:

0.1009
1.8307e-05
1.6907e-05
1.6800e-05
1.6817e-05

导入的背景很少,第一次遇到导入时,需要做一些工作才能将其添加到命名空间中,同一模块的后续导入几乎是无操作的。结果表明"请求"是第一次加载它调用。在 timeit 调用之前和之后打印 globals(( 证实了这一点。在模块顶部插入"导入请求"将导致第一个结果与其他结果相同,再次证实了该理论,但并不总是实际解决方案。

增加回合数将减少第一轮的影响,但可能仍然很重要。在这种情况下,number=100000 为第一次调用提供 0.12 (0.10 + 1.70e-5*100000(,为其他调用提供 0.017 (1.70e-5*100000(。

对于它的价值,我有一个扔掉的数字= 1调用来计时,然后继续它。

是的,python在第一次运行后缓存pyc文件,因此如果代码没有更改,它将在下一次迭代中运行得更快,因为它不需要编译它以再次byte代码。请记住,python是一种解释性语言,它只是跳过了一个interpretation步骤。

相关内容

  • 没有找到相关文章

最新更新