优化MATLAB代码指南

时间:2013-11-01 07:20:38

标签: matlab optimization

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

常见问题:

  • 为我优化此代码
  • 如何对此进行矢量化?

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

优化Matlab代码是一种黑色艺术,总有一种更好的方法。有时甚至无法对代码进行矢量化。

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

1 个答案:

答案 0 :(得分:14)

前言

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

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

注意:我在代码中的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 秒),但我试图表示矢量化不是一个选项的更复杂的时间。

我最近发现零序功能(以及性质相同的其他功能)在预分配时并不像简单地将最后一个值设置为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文件或类似文件,以便日后可以快速阅读。
  3. 如何预先分配复杂的结构?

    正如我们已经看到的那样,为简单数据类型预分配空间很容易,但是如果它是一个非常复杂的数据类型,例如结构体结构呢?

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

    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迭代多次调用plothold 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);
    

    我已经使用这种方法打印了数千条细线,性能改进非常可观。不仅在初始情节中,而且后续操作(如缩放或平移操作)的性能也得到了改善。