MATLAB中的代码优化有两种重要的方法:预分配组和向量化循环。
我们举一个简单的例子来看,创建一个MATLAB函数来计算f(x) = sin(x / 100π):
function y = sinfun1(M)
x = 0: M - 1;
for k = 1: numel(x)
y(k) = sin(x(k) / (100 * pi));
end
这里 我们使用函数timeit来计算调用函数所需的时间。(timeit可用于得到函数调用的可靠的、可重复的时间测量。)
此时我们得到M数量级为20000时的时间测量,如图1所示:
我们在编写完函数sinfun1后可以看到MATLAB编辑器给出了一个提示:变量’y’似乎会随迭代次数而改变,请预分配内存以获得更高的运算速度。
为什么会出现这样的情况?那是因为在sinfun1这个函数中,输出变量y每经过一次循环后都会增长一个元素大小,它必须重新分配新的存储空间,并且在每次数组生长时都要复制前一组数组元素。这种频繁的内存重新分配和复制的开销非常大,与sin本身的计算相比需要更多的时间。
此时我们采用预分配数组的办法进行尝试:
预分配组:是指在进入一个计算数组元素的for循环之前,初始化数组。预分配就意味着咋循环开始之前把它初始化为所希望的输出大小。
function y = sinfun2(M)
x = 0: M - 1;
y = zeros(1, numel(x));
for k = 1: numel(x)
y(k) = sin(x(k) / (100 * pi));
end
在这里我们使用了zeros生成一个矩阵来预分配存储空间,然后调用timeit计算M=20000时候的时间测量,如图2:
向量化循环是指使用矩阵/向量运算符、索引技术和现有的MATLAB或工具箱函数来完全消除循环的一种技术。
我们使用向量化循环对矩阵的输入进行逐元素的操作来消除循环:
function y = sinfun3(M)
x = 0: M - 1;
y = sin(x ./ (100 * pi));
end
此时我们再用timit在M=20000时进行时间测量,如图5:
由图可知我们使用向量化时,在M=20000时要比预分配数组快3倍。接下来测量M的数量级为200000和2000000的时间。如图6所示:
这个例子是基于公式f(x) = Asin(u0x + v0y)创建一幅合成图像,首先使用嵌套的for循环来计算f:
function f = twodsin1(A, u0, v0, M, N)
%f = zeros(M, N);
for c = 1 : N
v0y = v0 * (c - 1);
for r = 1 : M
u0x = u0 * (r - 1);
f(r, c) = A * sin(u0x + v0y);
end
end
在实验中使用一个meshgrid的MATLAB函数将函数重写为没有for循环的形式。
function f = twodsin2(A, u0, v0, M, N)
r = 0 : M - 1;
c = 0 : N - 1;
[C, R] = meshgrid(c, r);
f = A * sin(u0 * R + v0 * C);
综合以上两个实验表明,两种代码优化方法(预分配数组和向量化循环)在具体的使用中的差别并不是很大,都可以提升循环运行的速度。我们可以在遇到具体问题后,根据具体情况选择具体的方法:若是循环存在没有预分配内存的问题,那我们可以考虑采用预分配的方法,这样对代码的改动不大,更直观,也更容易表示我们代码得到实际工作机理;若是确定没有预分配的问题,就可以选择向量化循环的办法,这样与基于循环的代码相比,向量化后的代码更易于阅读,更为简洁。