MATLAB函数第一次运行缓慢,但随后更快。为什么?

时间:2013-10-21 15:55:35

标签: matlab benchmarking

我有一个很大的MATLAB函数文件。它首先创建一个零矩阵,然后通过评估函数中硬编码的多个相应(长)代数表达式来更新大约70%的单元格。完成后,将返回一个数字矩阵。

.m文件大约4 MB(我有100个m。文件,但这并不直接相关)。当我第一次评估该功能时,需要大约9秒来评估。然而,后续的运行只需要大约0.1秒,这正是我所期待的。

为什么第一次评估需要9秒钟?每当我关闭并重新打开MATLAB时,我每次都会进行这种缓慢的第一次评估,后续运行速度要快得多。这是为什么?

m。文件可以在以下公共链接中找到(您可以从浏览器复制文本): https://dl.dropboxusercontent.com/u/157153767/K_a_12_102x.m

您应该使用的命令窗口输入是: [测试] = K_a_12_102x(414000000,1.1095e + 09,1.2500e-04,0.0840,0.0840,0.0240,0.0240,0.0020,0.0020,0,0,0,0,3.0397e + 08,8.9930e + 07,0, 3.0397e + 08,0,1.0702e + 08,0,0,0,0,0,0,497.7389,80.7355,-15.9811,391.1985,-15.9811,103.5248,20440000,0,20440000,0.06)

3 个答案:

答案 0 :(得分:14)

在将JIT编译器引入MATLAB之前很久就已经出现了慢速运行,而在未应用JIT编译器的情况下,即使对于MEX文件,也是如此。当您第一次运行代码时,MATLAB必须从磁盘加载它,解析代码(请参阅下面的运行时类型分析详细信息),如果它是.m文件,则应用JIT编译。然后在执行时,分配数据空间,并将指令加载到CPU缓存中,在那里它们可以保持非常快的访问时间以进行进一步的执行。这就是在MATLAB世界之外无处不在的“缓存升温”程序的原因,正如我所理解的那样(为我挥手致意硬件增益)。但是,对于.m fies,磁盘访问可能是一个重要因素,即使文件比“大约4MB大”小得多,就像你的情况一样。 function disambiguation when multiple functions have the same name还增加了一步。

要查看MEX文件的这种情况,只需运行clear mex并为函数调用计时。 MATLAB必须重新从磁盘加载到内存中,可能是CPU缓存无效。

运行时类型分析

代码加速功能的第二个方面(JIT代码生成是第一个),是运行时类型分析。来自旧的MathWork白皮书:

  

运行时类型分析基于以下前提:如果之前已经处理了一行M代码,那么变量很可能具有与上次系统看到的相同的类型和形状线。 第一次执行一行代码时,系统会检查变量,并为找到的数据类型和形状生成特定代码。只要系统验证变量类型和大小没有改变,该行的后续执行就可以重用该代码。 由于类型很少更改,后续执行会尽快运行。如果类型确实发生更改,则会重新生成代码。

你可能会考虑JIT编译过程的这一部分,它是。但关键是这个分析是在第一次执行时运行的,无论加速器是否决定JIT编译任何代码行。顺便说一句,整个文件没有编译成机器代码。以前可以使用setpref('profiler','showJitLines',1);在分析器中查看which lines got accelerated,但遗憾的是,该功能已被删除。

无论如何,在实际查看你的代码之后,从磁盘加载文件后需要解析大量的常量和变量。一行超过31,000个字符,数千个数字文字!分析和决定需要编译的内容以及运行之间可以缓存的内容有很多。好像要证明这一点,只需查看(不运行)您的代码就会在堆栈跟踪上使用DirectUI::DUIXmlParser::InitializeParserFromXmlLiteReader使编辑器崩溃。哎呀,这是一些讨厌的代码!

JIT编译器是否为此函数生成代码?

让我们来看看打开MATLAB加速功能的代码。我们还运行控制测试,我们知道在没有加速的情况下运行速度大约8倍。

>> feature accel on
>> clear K_a_12_102x
>> x = rand(1000); tic,for t=1:1e6, x=x; end,toc % control
Elapsed time is 0.083878 seconds.
% do first-run of K_a_12_102x, took 13.280327 sec
>> tic; [test]=K_a_12_102x(414000000,1.1095e+09,1.2500e-04,0.0840,0.0840,0.0240,0.0240,0.0020,0.0020,0,0,0,0,3.0397e+08,8.9930e+07,0,3.0397e+08,0,1.0702e+08,0,0,0,0,0,0,497.7389,80.7355,-15.9811,391.1985,-15.9811,103.5248,20440000,0,20440000,0.06); toc
Elapsed time is 0.151804 seconds.

现在我们关闭加速并运行相同的测试:

>> feature accel off
>> clear K_a_12_102x
>> tic,for t=1:1e6, x=x; end,toc % control
Elapsed time is 0.630039 seconds.
% do a first-run of K_a_12_102x, took 15.634775 seconds
>> tic; [test]=K_a_12_102x(414000000,1.1095e+09,1.2500e-04,0.0840,0.0840,0.0240,0.0240,0.0020,0.0020,0,0,0,0,3.0397e+08,8.9930e+07,0,3.0397e+08,0,1.0702e+08,0,0,0,0,0,0,497.7389,80.7355,-15.9811,391.1985,-15.9811,103.5248,20440000,0,20440000,0.06); toc
Elapsed time is 0.159683 seconds.

首饰

调查结果有两方面:

  1. 加速(JIT)禁用时,首次运行时间没有改善(JIT开启时为13.28秒,关闭JIT时为15.63秒)。
  2. 后续运行显示启用JIT时不会生成机器代码(JIT ON为0.1518秒,JIT关闭为0.1597秒)
  3. 简而言之,您的代码不会受益于JIT加速,JIT执行/分析也不会增加首次执行时间。

    问题仍然存在,是什么原因导致首次运行缓慢?一些可能性:从磁盘加载代码文本,在将代码保存到RAM中之前解析(剥离注释和空白)代码,而不是重用以前运行中保存的变量初始化,可能是保存在CPU缓存中的函数使用的核心MATLAB指令,以及任何MATLAB执行运行时语法检查所必需的非JIT相关代码分析。事实上,文件是4MB,并且在方程长度和数字文字数量方面非常复杂,这表明它不是CPU缓存,而是初始文件加载和代码分析。

答案 1 :(得分:5)

我认为这是JIT编译。第一次执行文件时,MATLAB必须解释它(将文本转换为机器代码)。

后续运行使用缓存的机器代码并执行得更快。要验证这一点:在代码中进行少量更改后,MATLAB将需要重做此编译 - 因此下次运行时应该很慢。 (我这样做的确如此。)你正在做大量简单的操作,应该很快执行。转换为机器代码会降低您的速度。

加速整个过程:将代码转移到C,C#或类似的东西,并将其作为DLL文件包含在内。您将获得持续快速的计算,但无法轻松更改它们。

(使用C#DLL文件,你也有一些JIT编译,但它的频率较低,可能仍然比MATLAB快。)


我已完成一些编码并将代码移植到C#。原始时间为13.4秒和0.15秒(第一次/第二次运行)

简单端口,发布配置:

>> test_1
Elapsed time is 124.7 seconds.
>> test_1
Elapsed time is 0.0297 seconds.

所以第一次运行比MATLAB更糟糕 - 更糟糕。虽然我喜欢C#,但它可能不是这项工作的最佳工具。 (顺便说一句:我不得不使用命令行编译器,因为Visual Studio 2010一直在崩溃......)

答案 2 :(得分:3)

除了TheCrumbMonster提到的JIT编译器的效果之外,可能还会有各种caching效果。要么Matlab本身足够聪明以重用它的一些数据结构,要么它的一些代码已经存储在处理器的高速缓冲存储器中而不是存储在主存储器中。事实上,即使JIT本身依赖于缓存编译的结果,否则每次调用函数时都必须重新编译。此外,所有现代操作系统都进行各种缓存,因此您只需从内存中读取一些数据文件,MEX文件或DLL,而不是从内存中读取它。

这是为了准确测量某些函数的执行速度的原因之一,你不应该使用简单的tic(); toc()语句,而是使用类似timeit的函数(使用它,它非常好! )。这样可以多次重复测量以“预热”高速缓存,并始终丢弃前几次测量。

至于matlab对这个特定文件的速度慢的原因,我完全可以理解它。它需要我的文本编辑器打开它超过一分钟,你有大约100行具有非常长的表单声明

K_a_12=zeros(1089,100);
K_a_12(1011,1) = 2*h_a*((x*(250*G_a*L2^20*W + 5250*G_a*L1^2*L2^18*W + ...
K_a_12(1011,3) = 2*h_a*((x^13*(188955000*G_a*L2^8*W*h_1 +  ...

这是(我希望)自动生成的。在我看来,计算可以更有效地完成。首先,您似乎只填充矩阵的一小部分,因此您应该使用sparse matrix。接下来,通过快速检查,每个术语看起来都是h_a * x^n1 * const * L1^n2 * L2^n3 * ....形式,可以用简化的方式计算。我敢打赌,通过几个矩阵的乘法和取幂可以在几行中完成整个计算,这些矩阵应该保存为mat文件,而不是像完整写出的计算。最后,您也不会使用函数的大约一半输入参数。