我注意到许多关于 SO 的个别问题,但没有一个很好的 MATLAB 优化指南。
常见问题:
- 为我优化这段代码
- 我如何矢量化它?
我认为这些问题不会停止,但我希望这里提出的想法能够集中参考。
优化 Matlab 代码是一种魔法,总有更好的方法来做到这一点。有时,直接对代码进行矢量化是不可能的。
所以我的问题是:当矢量化不可能或极其复杂时,您有哪些优化 MATLAB 代码的技巧和窍门?此外,如果您有任何常见的矢量化技巧,我也不介意看到它们。
我注意到许多关于 SO 的个别问题,但没有一个很好的 MATLAB 优化指南。
常见问题:
我认为这些问题不会停止,但我希望这里提出的想法能够集中参考。
优化 Matlab 代码是一种魔法,总有更好的方法来做到这一点。有时,直接对代码进行矢量化是不可能的。
所以我的问题是:当矢量化不可能或极其复杂时,您有哪些优化 MATLAB 代码的技巧和窍门?此外,如果您有任何常见的矢量化技巧,我也不介意看到它们。
所有这些测试都是在与他人共享的机器上执行的,因此它不是一个完全干净的环境。在每次测试之间,我都会清除工作区以释放内存。
请不要关注个别数字,只看优化前后的时间差异。
注意:我在代码中放置的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秒),但我试图表示无法选择矢量化的更复杂的时间。
我最近发现 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秒
这显然不是完美的预分配,之后翻转数组需要一点时间,但时间的改进不言自明。我希望有人有更好的方法来做到这一点,但同时这是一个非常好的黑客。
在内存使用方面,结构数组比数组结构差几个数量级:
% 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.
但是,您可以使用skip
fread 的值来实现与以前相同的优化:
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 中执行此操作的传统方法是迭代多次调用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);
我已经使用这种方法打印了数千条细线,并且性能提升是巨大的。不仅在初始绘图中,后续操作(例如缩放或平移操作)的性能也有所提高。