优化MATLAB代码指南

塞缪尔·奥马利

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

常见问题:

  • 为我优化此代码
  • 如何将此向量化?

我认为这些问题不会停止,但是我希望这里提出的想法可以使它们集中起来供参考。

优化Matlab代码有点白费力气,总有更好的方法来做到这一点。有时直接向量化代码是不可能的。

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

塞缪尔·奥马利

前言

所有这些测试都是在与他人共享的计算机上执行的,因此这不是一个完美的环境。在每次测试之间,我都会清理工作空间以释放内存。

请不要关注单个数字,只需查看优化前后前后的时间差异即可。

注意:我在代码中放置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文件另存为文件或类似文件,以便日后快速读取。

如何预分配复杂的结构?

正如我们已经看到的,为简单的数据类型预分配空间很容易,但是如果它是一个非常复杂的数据类型(如结构的struct)怎么办?

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

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);

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章