我注意到很多关于SO的问题,但没有一个很好的MATLAB优化指南。
常见问题:
我不认为这些问题会停止,但我希望这里提出的想法能让他们集中参考。
优化Matlab代码是一种黑色艺术,总有一种更好的方法。有时甚至无法对代码进行矢量化。
所以我的问题是:当矢量化不可能或极其复杂时,优化MATLAB代码的一些提示和技巧是什么?此外,如果你有任何常见的矢量化技巧,我也不介意看到它们。
答案 0 :(得分:14)
所有这些测试都是在与他人共享的计算机上执行的,因此它不是一个非常干净的环境。在每次测试之间,我清除工作区以释放内存。
请注意不要注意个别数字,只需看看优化时间之前和之后的差异。
注意:我在代码中的tic
和toc
来电显示我在哪里测量时间。
在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 秒),但我试图表示矢量化不是一个选项的更复杂的时间。
我最近发现零序功能(以及性质相同的其他功能)在预分配时并不像简单地将最后一个值设置为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 秒
这显然不是完美的预分配,之后需要一段时间来翻转阵列,但时间的改进不言而喻。我希望有人有更好的方法来做到这一点,但同时这是一个非常好的黑客。
就内存使用而言,结构数组的数量级比结构数组差:
% 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 字节
正如您所看到的,即使在这个简单/小例子中,Structs数组也比结构数组使用更多的内存。如果你想绘制数据,那么阵列结构也是一种更有用的格式。
每个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.
但是,您可以使用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;
因此,使用两个fread
而不是200来完成此文件读取,这是一个巨大的改进。
我最近处理过一些使用了很多函数调用的代码,所有函数调用都位于不同的文件中。所以我们说有100个单独的文件,都互相调用。通过"内联"将此代码转换为一个函数,我发现执行速度从9秒提高了20%。
显然你不会以牺牲可重用性为代价来做这件事,但在我的情况下,这些功能是自动生成的,根本不会重复使用。但是我们仍然可以从中学到这一点,并避免在不需要的情况下进行过多的函数调用。
外部MEX功能会导致被调用的开销。因此,对大型MEX函数的调用比对较小MEX函数的许多调用要高效得多。
在绘制断开连接的数据(例如一组垂直线)时,在Matlab中执行此操作的传统方法是使用line
迭代多次调用plot
或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);
我已经使用这种方法打印了数千条细线,性能改进非常可观。不仅在初始情节中,而且后续操作(如缩放或平移操作)的性能也得到了改善。