7

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

.m 文件大约 4 MB 大(我有 100 个这些 m. 文件,但这并不直接相关)。当我第一次评估函数时,评估大约需要 9 秒。然而,随后的运行只需要大约 0.1 秒,这比我预期的要多。

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

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

您应该使用的命令窗口输入是: [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)

4

3 回答 3

15

早在将 JIT 编译器引入 MATLAB 之前,首次运行速度就很慢,即使是 MEX 文件也是如此,在其上未应用 JIT 编译器。当您第一次运行代码时,MATLAB 必须从磁盘加载它,解析代码(请参阅下面的运行时类型分析详细信息),如果它是 .m 文件,则应用 JIT 编译。然后在执行时,为数据分配空间,并将指令加载到 CPU 缓存中,在那里它们可能会保持非常快的访问时间以供进一步执行。据我了解,这就是在 MATLAB 世界之外无处不在的“缓存预热”程序的原因(对我的挥手致意的硬件爱好者表示歉意)。但是,对于 .m 文件,磁盘访问可能是一个重要因素,即使文件比您的情况“大约 4MB 大”小得多。当多个函数具有相同的名称时,还增加了一个函数消歧步骤。

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

运行时类型分析

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

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

您可能会考虑 JIT 编译过程的这一部分,确实如此。但关键是,无论加速器是否决定对任何代码行进行 JIT 编译,这种分析都是在第一次执行时运行的。顺便说一句,整个文件不会被编译成机器代码。过去可以看到在分析器中使用哪些行得到了加速setpref('profiler','showJitLines',1);,但不幸的是,它作为一个特性被删除了。

无论如何,在实际查看您的代码之后,从磁盘加载文件后需要解析的常量和变量数量惊人。一行超过 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 时不会生成任何机器代码(0.1518 秒,JIT 开启与 0.1597 秒,JIT 关闭)

简而言之,您的代码不会从 JIT 加速中受益,并且 JIT 执行/分析不会增加首次运行的执行时间。

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

于 2013-10-21T17:15:25.917 回答
4

我认为这就是 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 不断崩溃......)

于 2013-10-21T16:04:32.730 回答
3

除了 TheCrumbMonster 提到的 JIT 编译器的影响之外,可能还会发生各种缓存影响。要么 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 +  ...

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

于 2013-10-21T16:23:23.370 回答