我一直在测试一段代码的一些优化(特别是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.timeit
或timeit.repeat
或其他此类机制(。
pyc
文件是在导入没有pyc
文件或其pyc
文件过期的模块时创建的。它们不是为timeit
代码段创建的,即使您的定时代码来自导入的模块,典型的timeit
使用模式也会在计时开始之前导入模块。
您调用timeit.default_timer
而不是让timeit
按照其设计的工作方式处理事情,但即便如此,任何pyc
文件创建都不太可能在定时代码内发生。
是另一种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
步骤。