8

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

常见问题:

  • 为我优化这段代码
  • 我如何矢量化它?

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

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

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

4

1 回答 1

15

前言

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

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

注意:我在代码中放置的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 秒!),而且从那时起每次访问它时都会变慢。事实上,如果您绝对不能预先分配,那么在开始使用之前将矩阵复制到新的预先分配的矩阵仍然很有用。

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

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

  1. 高估 -严重高估矩阵的大小并分配太多空间比分配空间不足要好。
  2. 处理它并稍后修复 -我经常看到开发人员忍受缓慢的填充时间,然后将矩阵复制到新的预分配空间中。通常将其保存为.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

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

数据结构

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

% 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字节

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

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

文件读取

您在代码中的数量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.

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

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;

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

函数调用

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

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

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

绘制许多断开的线

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

我发现的技术使用了这样一个事实,即您可以将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);

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

于 2013-11-01T07:20:38.497 回答