11

在 MATLAB 中高效编程学习的第一件事是避免动态调整数组大小。标准示例如下。

N = 1000;

% Method 0: Bad
clear a
for i=1:N
    a(i) = cos(i);
end

% Method 1: Better
clear a; a = zeros(N,1);
for i=1:N
    a(i) = cos(i)
end

这里的“坏”变体需要 O( N^2) 时间来运行,因为它必须分配一个新数组并在循环的每次迭代中复制旧值。

我自己在调试时首选的做法是分配一个数组NaN,比 . 更难与有效值混淆0

% Method 2: Easier to Debug
clear a; a = NaN(N,1);
for i=1:N
    a(i) = cos(i)
end

然而,有人会天真地认为,一旦我们的代码被调试,我们分配一个数组然后用0or填充它是在浪费时间NaN。如here所述,您或许可以创建一个未初始化的数组,如下所示

% Method 3 : Even Better?
clear a; a(N,1) = 0;
for i=1:N
    a(i) = cos(i);
end

但是,在我自己的测试(MATLAB R2013a)中,我注意到方法 1 和 3 之间没有明显区别,而方法 2 需要更多时间。这表明 MATLAB 在a = zeros(N,1)调用时避免了将数组显式初始化为零。

因此,我很想知道

  • 在 MATLAB 中预分配(未初始化)数组的最佳方法是什么?(最重要的是,大型阵列)
  • 这也适用于八度吗?
4

2 回答 2

8

考试

使用 MatLab 2013b I 和 Intel Xeon 3.6GHz + 16GB RAM,我运行下面的代码进行分析。我区分了 3 种方法,只考虑了一维数组,即向量。方法 1 和 2 已经使用列向量和行向量进行了测试,即 (n,1) 和 (1,n)。

方法 1 (M1R, M1C)

a = zeros(1,n);

方法 2 M2R、M2C

a = NaN(1,n);

方法 3 (M3)

a(n) = 0;

结果

时序结果和元素数量已在图时序1D 中以双对数刻度绘制。

计时1d

如图所示,第三种方法的分配几乎与向量大小无关,而另一种方法稳步增加,表明向量的隐式定义。

讨论

MatLab 使用 JIT (Just in time) 做了很多代码优化,即在运行时进行代码优化。因此,提出运行速度更快的代码部分是由于编程(无论是否优化始终相同)还是由于优化是一个有效的问题。要测试此优化,可以使用 feature('accel','off') 关闭。再次运行代码的结果相当有趣:

计时1Dnoacceleration

结果表明,现在方法 1 对于行向量和列向量都是最优的。方法 3 的行为类似于第一个测试中的其他方法。

结论

优化内存预分配是无用的,而且浪费时间,因为 MatLab 无论如何都会为您优化。

请注意,内存应该是预先分配的,但您执行它的方式并不重要。预分配内存的性能很大程度上取决于 MatLab 的 JIT 编译器是否选择优化您的代码。这完全取决于 .m 文件的所有其他内容,因为编译器当时会考虑代码块然后尝试优化(它甚至有内存,因此多次运行文件可能会导致执行率更低 -时间)。与之后执行的计算相比,考虑到性能,内存预分配通常是一个非常短的过程

在我看来,应该使用方法 1 或方法 2 预先分配内存,以维护可读的代码并使用 MatLab 帮助建议的功能,因为这些是未来最有可能改进的功能。

使用的代码

clear all
clc
feature('accel','on')

number1D=30;

nn1D=2.^(1:number1D);

timings1D=zeros(5,number1D);

for ii=1:length(nn1D);
    n=nn1D(ii);
    % 1D
    tic
    a = zeros(1,n);
    a(randi(n,1))=1;
    timings1D(1,ii)=toc;
    fprintf('1D row vector method1 took: %f\n',timings1D(1,ii))
    clear a

    tic
    b = zeros(n,1);
    b(randi(n,1))=1;
    timings1D(2,ii)=toc;
    fprintf('1D column vector method1 took: %f\n',timings1D(2,ii))
    clear b

    tic
    c = NaN(1,n);
    c(randi(n,1))=1;
    timings1D(3,ii)=toc;
    fprintf('1D row vector method2 took: %f\n',timings1D(3,ii))
    clear c

    tic
    d = NaN(n,1);
    d(randi(n,1))=1;
    timings1D(4,ii)=toc;
    fprintf('1D row vector method2 took: %f\n',timings1D(4,ii))
    clear d

    tic
    e(n) = 0;
    e(randi(n,1))=1;
    timings1D(5,ii)=toc;
    fprintf('1D row vector method3 took: %f\n',timings1D(5,ii))
    clear e
end
logtimings1D = log10(timings1D);
lognn1D=log10(nn1D);
figure(1)
clf()
hold on
plot(lognn1D,logtimings1D(1,:),'-k','LineWidth',2)
plot(lognn1D,logtimings1D(2,:),'--k','LineWidth',2)
plot(lognn1D,logtimings1D(3,:),'-.k','LineWidth',2)
plot(lognn1D,logtimings1D(4,:),'-','Color',[0.6 0.6 0.6],'LineWidth',2)
plot(lognn1D,logtimings1D(5,:),'--','Color',[0.6 0.6 0.6],'LineWidth',2)
xlabel('Number of elements (log10[-])')
ylabel('Timing of each method (log10[s])')
legend('M1R','M1C','M2R','M2C','M3','Location','NW')
title({'Various methods of pre-allocation in 1D','nr. of elements vs timing'})
hold off

笔记

包含c(randi(n,1))=1; 除了将值 1 分配给预分配数组中的随机元素之外,不要做任何事情,以便使用该数组来稍微挑战 JIT 编译器。这些线不会显着影响预分配测量,即它们不可测量并且不影响测试。

于 2014-07-31T08:06:32.160 回答
1

让 Matlab 为您处理分配怎么样?

clear a;
for i=N:-1:1
    a(i) = cos(i);
end

然后,Matlab 可以用它认为是最佳的(可能为零)分配和填充数组。但是,您没有调试优势NaNs

于 2014-07-30T12:22:40.330 回答