为什么Matlab在脚本“热身”后运行更快?

19

我注意到第一次运行脚本比第二次和第三次需要更多的时间1。在这个问题中提到了“预热(warm-up)”,但没有解释。

为什么代码在“预热”之后会更快运行?

我没有clear all在每个函数调用之间清除所有内容2,但是输入参数对于每个函数调用都有所改变。有人知道为什么吗?


1. 我的许可证是本地的,所以这不是与许可证检查相关的问题。

2. 实际上,即使我clear all,行为也不会改变。

4个回答

9
第一次运行更快的原因之一是许多东西只初始化一次,其结果被缓存并在下一次重复使用。例如,在M端,变量可以在函数中定义为persistent,这些函数可以被locked。这也可以发生在MEX-side上。
此外,许多依赖项在第一次加载后保留在内存中以供重新使用。这包括M函数、OOP类、Java类、MEX函数等。这适用于内置和用户定义的函数。
例如,在第一次运行脚本之前和之后,发出以下command,然后进行比较:
[M,X,C] = inmem('-completenames')

请注意,clear all并不一定会清除以上所有内容,更不用说被锁定的函数了...
最后,我们不要忘记加速器的作用。它不是每次调用函数时都解释M代码,而是在运行时将其编译成机器代码指令。JIT编译仅在第一次调用时发生,因此理想情况下,运行对象代码的效率将克服每次重新解释程序的开销。

谢谢Amro!你知道如果只将必要的程序放入P-code缓冲区,而不是所有其他函数调用中使用的东西,脚本是否会运行得更快吗?也就是说,在对特定脚本进行基准测试之前,我应该执行clear all操作吗?我还没有注意到明显的差异,但我也没有“填满”缓冲区... - Stewie Griffin
1
Amro,除非你的代码很小,否则JIT编译是加速第一次运行后代码的主要影响 - 加载函数并不重要。此外,JIT编译不仅发生在第一次调用上 - MATLAB有时只会在第一次运行时JIT编译代码的部分,而其他部分稍后再进行。@RobertP.,如果你需要对代码进行基准测试,那么在你引用的问题中链接的timeit函数是最好的方法。除其他事项外,它还为您处理了所有你所描述的“预热”问题。 - Sam Roberts
@SamRoberts:我同意,这确实是JIT编译的承诺。如果一段代码被重复调用,我们期望JIT编译器在运行时检测到这个“热点”,并将其优化为直接的机器码。希望即使不是在同一次运行中也能实现。至于函数加载,我认为这更多是MEX函数的问题,因为加载外部依赖项的开销更高。 - Amro
@RobertP:我怀疑清除内存不会使程序运行更快。即使它确实可以,你也不应该依赖语言实现的细节来优化程序性能。始终编写惯用代码。 - Amro

2

Matlab是解释执行的。如果你不预热代码,那么由于解释而非实际算法,你会浪费很多时间。这可能会严重影响计时结果。

至少运行一次代码将使Matlab能够真正编译适当的代码段。


1
除了Matlab特定的原因,如JIT编译,现代CPU具有大型缓存、分支预测器等等。即使在汇编语言中,这些都是基准测试中需要注意的问题。
此外,更重要的是,现代CPU通常以低时钟速度空闲,并且只有在持续负载几毫秒后才会跳转到全速运行。
英特尔的Turbo功能更加奇妙:当功率和热量限制允许时,CPU可以运行得比其可持续的最大频率更快。因此,如果您没有小心控制这些因素,您的基准测试的前20秒至1分钟可能比其余部分运行得更快。

0
另一个问题,AmroMarc 没有提到的是内存(预)分配。 如果您的脚本没有预先分配其内存,则由于内存分配,其第一次运行将非常缓慢。一旦完成了第一次迭代,所有内存都已分配,因此后续脚本的调用将更有效率。
举个例子。
for ii = 1:1000
    vec(ii) = ii; %// vec grows inside loop the first time this code is executed only
end

3
@Shai 在预分配方面是正确的,但我不同意其后果。像这样做的代码是糟糕的编写。预热这种代码会使下次运行更快,但在这种情况下,算法本身就有问题。这就是为什么我认为,在这种情况下,预热实际上会使计时偏向于低效的代码并掩盖了不良实现的缺点! - Marc Claesen
@Shai 预分配是一件好事,但它对第一次和后续调用具有相同的影响。在你的例子中,vec 不仅在代码第一次执行时增长,而且每次执行代码时都会增长。 - Sam Roberts
@Shai,抱歉,这并不完全正确,因为这是在脚本内部,所以如果您再次运行脚本,vec已经存在。如果它在一个函数中,它会在每次执行代码时增长,而不仅仅是第一次。 - Sam Roberts
@SamRoberts - 这取决于您如何初始化 vec。如果您在脚本中使用循环来初始化 vec,则它将在脚本第一次运行时增长,并且在第二次(以及所有后续)运行时不会更改大小。 - Shai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接