相信大家在使用matlab时候经常会收到程序运行太慢的困扰,当程序比较复杂时,常常需要很长时间等待。我有个朋友就是这样,每次debug都要很长时间,等着的时候就想耍会手机,结果耍完一抬头发现程序运行结束了,但时间已经过去半天了。
一般来说,程序运行太慢都是因为循环的存在,使用双层甚至多层循环会使得程序运行效率极低。这篇博客将重点介绍如何在Matlab中避免使用循环语句并提高程序效率。我们将深入讨论向量化运算、预分配空间和相关函数(cellfun、arrayfun和structfun等)的用法,同时配有详细的示例代码和解释,帮助大家更好地掌握这些技术和优化方法。
1.记录程序运行时间
为了比较使用循环语句与否对程序运行时间的影响,我们需要用到matlab中的tic,toc语句,tic函数表示开始计时。执行tic语句后,程序就会记录下当前时间。接下来的代码运行时,而程序会在toc函数调用时输出自tic函数到当前时刻所经过的时间。例如:
tic % 开始计时 % 模块代码 x = 1:10000000; y = sin(x); disp(y(1:10)); toc % 打印出程序自tic调用以来经过的时间
运行结果:
如果你不想在命令行输出运行时间,也可以将程序运行时间存在变量中,避免在命令行输出,例如:
tic % 开始计时 % 模块代码 x = 1:10^8; y = sin(x); time0=toc; % 将程序自tic调用以来经过的时间存在变量time0中
2.向量化运算
为了提高程序性能,我们可以使用向量化运算来避免循环,同时实现对向量中所有元素的操作。向量化运算是一种在Matlab中广泛使用的高效操作方式,它可以对整个向量或矩阵进行运算,而不需要使用循环。具体来说,向量化运算使用内部优化的编译器,通过单个指令完成多个元素的处理,因此能够大大提高程序的效率。在Matlab中,常用的向量化运算符包括 .*, ./, .^, .', .*,等。具体的用法如表1所示:
表1 向量化运算符号的用法
运算符 | 用法 | 说明 |
.* | A .* B | A 数组和 B 数组中对应元素逐个相乘 |
./ | A ./ B | A 数组和 B 数组中对应元素逐个相除 |
.^ | A .^ B | A 数组和 B 数组中对应元素逐个做幂运算 |
.* | A .* B | B 对应 A 的每一列进行元素相乘运算 |
首先举一个简单的例子来介绍如何使用向量化运算来代替循环语句。假设有两个矩阵 x 和 y,都包含500×500×500个元素,现在你想将它们每个元素相乘,然后将乘积相加。如果如果使用循环语句,可以这样写代码:
tic x=rand(500,500,500); y=rand(500,500,500); c = 0; for k = 1:500 for kk = 1:500 for kkk = 1:500 c = c + x(k,kk,kkk) * y(k,kk,kkk); end end end toc
运行时间需要11.25秒。
如果你使用向量化操作,代码会更加简单,运行时间也会更短:
tic x=rand(500,500,500); y=rand(500,500,500); c = sum(x(:) .* y(:)); toc
运行只要2.46秒,快了不止一点点,在这个例子中,x(:)和y(:)表示将矩阵x用一维向量的形式表示。x(:) .* y(:)表示将一维向量化之后的 x 和 y 中对应元素相乘,并得到一个新的向量化,然后使用 sum 函数对这个新向量求和,从而得到我们想要的结果。显然,使用向量操作代替循环,代码更简洁、更易读、更易扩展。当处理大型数据集时,向量操作也可以大大提高程序运行速度。
3.预分配空间
大家在使用Matlab编程时应该都看到过类似这样的警告:
这是在Matlab中,如果你想把一个新的元素添加到数组中,并且数组的大小没有指定或无法确定时,Matlab会重新分配整个数组并复制现有元素,这个过程会导致程序效率下降。当我们不得不使用循环语句时,我们也需要在使用变量前预分配空间。如果我们想在循环中构建一个数组时,你可以在循环之前预先分配数组空间,然后在循环中直接修改数组元素。这样做可以大大减少程序的运行时间和内存消耗。例如:
tic x=rand(2000,2000); y=rand(2000,2000); for k = 1:2000 for kk = 1:2000 c(k,kk) = x(k,kk) * y(k,kk); end end toc
上面的代码没有预分配内存,所需运行时间为4.45秒。如果在for循环之前初始化变量c,代码这样写:
tic x=rand(2000,2000); y=rand(2000,2000); c=zeros(2000,2000); for k = 1:2000 for kk = 1:2000 c(k,kk) = x(k,kk) * y(k,kk); end end toc
运行时间变成了0.2秒。一个不起眼的变量初始化,可以让代码运行速率快这么多。所以,之后编程的过程中,只要遇到循环语句,一定要确保循环内部用到的变量都是预分配内存的。
这篇博客就先介绍这两种比较常用的方法,还有另一种使用相关函数的方法,我们将在下一篇博客单独进行介绍。