优化MATLAB代码指南

优化MATLAB代码指南,matlab,optimization,Matlab,Optimization,我注意到了很多关于MATLAB优化的个别问题,但没有一个很好的指南 常见问题: % 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 = zero

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

常见问题:

% 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);
  • 为我优化这个代码
  • 如何将其矢量化
我不认为这些问题会停止,但我希望这里提出的想法能为他们提供一些集中的参考

优化Matlab代码是一门黑色艺术,总有更好的方法。有时,直接将代码矢量化是不可能的

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

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

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

注意:我在代码中进行的
tic
toc
调用用于显示我在何处测量所用时间

预分配 在Matlab中预分配阵列的简单操作可以提供巨大的速度优势

tic;


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

toc;
这需要47秒

这需要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
    文件或类似文件,以便以后可以快速读取
  • 如何预分配复杂结构?

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

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

    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分钟

    这需要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 of Arrays
    a.a(1) = 1;
    a.b(1) = 2;
    a.a(2) = 3;
    a.b(2) = 4;
    
    正如您所看到的,即使在这个简单/小的示例中,结构数组也比数组的结构使用了更多的内存。此外,如果要打印数据,数组结构的格式更有用

    每个结构都有一个大的头,正如您所看到的,一个结构数组多次重复这个头,其中数组的结构只有一个头,因此使用的空间更少。这种差异在较大的阵列中更为明显

    文件读取 代码中的
    fread
    (或任何系统调用)数量越少越好

    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
    s而不是200来完成的,这是一个巨大的改进

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

    显然,您不会以牺牲可重用性为代价来实现这一点,但在我的例子中,函数是自动生成的
    % 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);