优化 MATLAB 代码指南

Posted

技术标签:

【中文标题】优化 MATLAB 代码指南【英文标题】:Guide to Optimizing MATLAB Code 【发布时间】:2013-11-12 09:35:03 【问题描述】:

我注意到许多关于 SO 的个别问题,但没有一个很好的 MATLAB 优化指南。

常见问题:

为我优化此代码 如何对其进行矢量化处理?

我认为这些问题不会停止,但我希望这里提出的想法能够集中参考。

优化 Matlab 代码是一种黑色艺术,总有更好的方法来做到这一点。有时,直接对代码进行矢量化是不可能的。

所以我的问题是:当矢量化不可能或极其复杂时,您有哪些优化 MATLAB 代码的技巧和窍门?另外,如果您有任何常见的矢量化技巧,我也不介意看到它们。

【问题讨论】:

相关问题:预分配***.com/q/21023171/1714410 感谢 Shai,如果您想出任何优化技巧,请随时更新此社区 Wiki 答案。 很好的问题和很好的倡议。 【参考方案1】:

前言

所有这些测试都是在与他人共享的机器上执行的,因此它不是一个完全干净的环境。在每次测试之间,我都会清除工作区以释放内存。

请不要关注个别数字,只看优化前后的时间差异。

注意:我在代码中调用的tictoc 是为了显示我在哪里测量所用时间。

预分配

在 Matlab 中预先分配数组的简单行为可以带来巨大的速度优势。

tic;


for i = 1:100000
    my_array(i) = 5 * i;
end

toc;

这需要 47

tic;

length = 100000;
my_array = zeros(1, length);

for i = 1:length
    my_array(i) = 5 * i;
end

toc;

这需要 0.1018

添加一行代码从 47 秒到 0.1 秒是一个惊人的改进。显然,在这个简单的示例中,您可以将其矢量化为 my_array = 5 * 1:100000(这需要 0.000423 秒),但我试图表示无法选择矢量化的更复杂的时间。

我最近发现 zeros 函数(以及其他性质相同的函数)在预分配方面不如简单地将最后一个值设置为 0 快:

tic;

length = 100000;
my_array(length) = 0;

for i = 1:length
    my_array(i) = 5 * i;
end

toc;

这需要 0.0991

现在显然这个微小的差异并不能证明太多,但你必须相信我,在一个包含许多优化的大文件中,差异变得更加明显。

为什么会这样?

预分配方法会分配一块内存供您使用。该内存是连续的并且可以预取,就像 C++ 或 Java 中的数组一样。但是,如果您不预先分配,那么 MATLAB 将不得不动态地找到越来越多的内存供您使用。据我了解,这与 Java ArrayList 的行为不同,更像是 LinkedList,其中数组的不同块被拆分到内存中的各个位置。

这不仅在您向其写入数据时会变慢(47 秒!),而且从那时起每次访问它时都会变慢。事实上,如果您绝对不能预先分配,那么在开始使用之前将矩阵复制到一个新的预先分配的矩阵中仍然很有用。

如果我不知道要分配多少空间怎么办?

这是一个常见问题,有几种不同的解决方案:

    高估 - 严重高估矩阵的大小并分配太多空间比分配空间不足要好。 处理它并稍后修复 - 我经常看到开发人员忍受缓慢的填充时间,然后将矩阵复制到新的预分配空间中。通常将其保存为.mat 文件或类似文件,以便日后快速阅读。

如何预分配复杂的结构?

为简单的数据类型预先分配空间很容易,正如我们已经看到的那样,但是如果它是一个非常复杂的数据类型,比如结构体的结构呢?

我永远无法明确地预先分配这些(我希望有人能提出更好的方法),所以我想出了这个简单的技巧:

tic;

length = 100000;

% Reverse the for-loop to start from the last element
for i = 1:length
    complicated_structure = read_from_file(i);
end

toc;

这需要 1.5 分钟

tic;

length = 100000;

% Reverse the for-loop to start from the last element
for i = length:-1:1
    complicated_structure = read_from_file(i);
end

% Flip the array back to the right way
complicated_structure = fliplr(complicated_structure);

toc;

这需要 6

这显然不是完美的预分配,之后翻转数组需要一点时间,但时间的改进不言自明。我希望有人有更好的方法来做到这一点,但同时这是一个很好的 hack。

数据结构

在内存使用方面,结构数组比数组结构差几个数量级:

% Array of Structs
a(1).a = 1;
a(1).b = 2;
a(2).a = 3;
a(2).b = 4;

使用 624 字节

% Struct of Arrays
a.a(1) = 1;
a.b(1) = 2;
a.a(2) = 3;
a.b(2) = 4;

使用 384 字节

如您所见,即使在这个简单/小示例中,结构数组使用的内存也比数组结构多得多。如果您想绘制数据,数组结构的格式也更​​有用。

每个结构都有一个大标题,正如您所见,一个结构数组会多次重复此标题,而数组结构只有一个标题,因此使用的空间更少。这种差异在更大的数组中更为明显。

文件读取

代码中freads(或任何与此相关的系统调用)的数量越少越好。

tic;    

for i = 1:100
    fread(fid, 1, '*int32');
end

toc;

前面的代码比下面的要慢很多:

tic;
fread(fid, 100, '*int32');
toc;

您可能认为这很明显,但同样的原则可以应用于更复杂的情况:

tic;

for i = 1:100
    val1(i) = fread(fid, 1, '*float32');
    val2(i) = fread(fid, 1, '*float32');
end

toc;

这个问题不再简单,因为在内存中浮点数是这样表示的:

val1 val2 val1 val2 etc.

但是,您可以使用 fread 的 skip 值来实现与以前相同的优化:

tic;

% Get the current position in the file
initial_position = ftell(fid);

% Read 100 float32 values, and skip 4 bytes after each one
val1 = fread(fid, 100, '*float32', 4);

% Set the file position back to the start (plus the size of the initial float32)
fseek(fid, position + 4, 'bof');

% Read 100 float32 values, and skip 4 bytes after each one
val2 = fread(fid, 100, '*float32', 4);

toc;

所以这个文件读取是使用两个 freads 而不是 200 个完成的,这是一个巨大的改进。

函数调用

我最近编写了一些使用许多函数调用的代码,所有这些函数都位于单独的文件中。因此,假设有 100 个单独的文件,它们都相互调用。通过将这段代码“内联”到一个函数中,我发现执行速度从 9 秒提高了 20%。

显然,您不会以可重用性为代价这样做,但在我的例子中,这些函数是自动生成的,根本没有被重用。但我们仍然可以从中吸取教训,避免在不需要的地方调用过多的函数。

调用外部 MEX 函数会产生开销。因此,对大型 MEX 函数的一次调用比对较小 MEX 函数的多次调用效率高得多。

绘制许多断开的线

在绘制断开的数据(例如一组垂直线)时,在 Matlab 中执行此操作的传统方法是使用 hold on 迭代多次调用 lineplot。但是,如果您要绘制大量单独的线,这将变得非常缓慢。

我发现的技术使用了这样一个事实,即您可以将NaN 值引入要绘制的数据中,它会导致数据中中断

以下人为设计的示例将一组 x_values、y1_values 和 y2_values(其中行从 [x, y1] 到 [x, y2])转换为适合对 plot 的一次调用的格式。

例如:

% Where x is 1:1000, draw vertical lines from 5 to 10.
x_values = 1:1000;
y1_values = ones(1, 1000) * 5;
y2_values = ones(1, 1000) * 10;

% Set x_plot_values to [1, 1, NaN, 2, 2, NaN, ...];
x_plot_values = zeros(1, length(x_values) * 3);
x_plot_values(1:3:end) = x_values;
x_plot_values(2:3:end) = x_values;
x_plot_values(3:3:end) = NaN;

% Set y_plot_values to [5, 10, NaN, 5, 10, NaN, ...];
y_plot_values = zeros(1, length(x_values) * 3);
y_plot_values(1:3:end) = y1_values;
y_plot_values(2:3:end) = y2_values;
y_plot_values(3:3:end) = NaN;

figure; plot(x_plot_values, y_plot_values);

我已经使用这种方法打印了数千条细线,并且性能得到了巨大的提升。不仅在初始绘图中,后续操作(例如缩放或平移操作)的性能也有所提高。

【讨论】:

+1 用于反向循环方法...我以前见过,但到目前为止,我从未使用过它。是否有可能及时将其变成一个社区维基? 我什么时候应该把它改成社区维基?我以前没做过。 我不知道,这实际上是一个真正的问题,询问是否有可能(如果您同意,是否可以)。我现在一直在meta上查找它,但我还没有找到一个好的答案。 编辑:如果答案更新足够多次,它会自动发生:See this FAQ post。 我已更改它,当我编辑它时有一个社区 wiki 复选框。如果您有任何问题,请随时添加到答案中

以上是关于优化 MATLAB 代码指南的主要内容,如果未能解决你的问题,请参考以下文章

单目标优化求解基于matlab天鹰优化算法求解单目标优化问题含Matlab源码 1856期

单目标优化求解基于matlab天鹰优化算法求解单目标优化问题含Matlab源码 1856期

多目标优化求解基于matlab自适应风驱动算法求解多目标优化问题含Matlab源码 1414期

优化算法差分进化优化杂草优化(DIWO)含Matlab源码 1482期

优化算法差分进化优化杂草优化(DIWO)含Matlab源码 1482期

PID优化基于matlab麻雀搜索算法PID优化设计含Matlab源码 1785期