当前位置: 首页 > article >正文

提高matlab运算效率——预分配内存

提高 matlab 的运算效率有很多技巧,比如用向量运算代替循环运算等。
这次介绍最常见的提高运算效率的方法——预分配内存
预分配内存是一件看上去非常平常以至于经常无视的技巧,但面对大量循环运算时,预分配内存能节约的时间是非常可观的。

最浪费时间的写法

下面这种最浪费时间的写法竟然是自己最常使用的,可见平时编程习惯很欠优化。

clear
tic
a_array = [];
for i = 1:1e5
    a = sin(i);
    a_array = [a_array;a];
end
toc

上面这种不断在原数组后补充新元素组成新数组的做法十分浪费时间。

  • 循环 1e4 次耗时 0.020635 秒。
  • 循环 1e5 次耗时 1.427629 秒。是 1e4 次的 69.18 倍。 1e4 次的 69.18 倍。
  • 循环 1e6 次耗时 763.709362 秒。是 1e5 次的 534.95 倍!

很明显看到,耗时并不随着循环次数线性增长,即并不是循环次数变为 10 倍则运行耗时也变为 10 倍。可见循环次数越多,这种编程方式的缺陷就越严重。

另一种没有预分配内存的写法

下面这种写法是按索引不断增补元素,运行效率比前一种有明显提高。但这种写法和预分配内存相比还是效率太低了。

tic
for i = 1:1e6
    b = sin(i);
    b_array(i) = b;
end
toc

tic
for i = 1:1e6
    b = sin(i);
    b_array(i) = b;
end
toc

上面的程序运行结果为

  • 历时 0.092180 秒。
  • 历时 0.018310 秒。
    可以看到相同的程序第二次运行耗时比一次要短得多。这是因为程序在第一次运行时已经为 b_array 分配了内存,因此节省了时间。由于不需要分配内存的时间,甚至可以看到这会比下面预分配内存的写法运行耗时还要短,这也侧面印证了预分配内存的有效性。
循环运行次数第一次运行第二次运行
1e50.002827 秒0.002714 秒
1e60.092180 秒0.018310 秒
1e71.543424 秒0.975414 秒

从上面的统计结果来看运行效率远远高于第一种方法。

预分配内存的写法

clear
tic
c_array = zeros(1, 1e5);
for i = 1:length(c_array)
    c = sin(i);
    c_array(i) = c;
end
toc
循环运行次数运行耗时
1e50.003003 秒
1e60.019552 秒
1e71.035434 秒

因此在实际编程中应为数组预分配内存,如果数组长度预先无法知晓,可以先预估一个长度分配。但是需要注意,如果预估的偏长则需要在最后将数组截断,否则数组后面会挂着一串零。
如果实在没有办法预估,也可以用第二种按索引增补元素的方法。但是如果增补的是数组时,那么还是采用预分配内存的方式更明智,具体原因见下。

面对补充数组时

当每次补充的是数组而不是单一元素时,预分配内存的优势将更加明显。
其实这种场景要更加常见,因为动态系统仿真时状态量往往不止一维,即状态量往往是数组。
此时对比上面第 2 和第 3 种写法的运算效率如下

%% 按索引补充数组
clear
tic
for i = 1:1e6
    b = [sin(i),sin(2*i),sin(3*i),sin(4*i),sin(5*i),sin(6*i),sin(7*i)];
    b_array(i,:) = b;
end
toc

%% 预分配内存
clear
tic
c_array = zeros(1e6,7);
for i = 1:length(c_array)
    c = [sin(i),sin(2*i),sin(3*i),sin(4*i),sin(5*i),sin(6*i),sin(7*i)];
    c_array(i,:) = c;
end
toc
循环运行次数按索引增补数组预分配内存
1e40.057479 秒0.003350 秒
1e54.324644 秒0.025172 秒
1e62041.857984 秒0.546501 秒

可以看出按索引增补数组的方法运算耗时呈爆炸式增长,在循环运行 1e6 次时耗时已经不可接受了。

面对补充数组时还有另一个常见的情况即数组的转置。这在系统的动态仿真中也是十分常见的。因为系统状态量一般习惯写为列向量,而最终结果为了方便和时间列向量对应又希望状态矩阵中每个时刻的状态是行向量,即每一行对应不同时刻。这就难免涉及到数组的转置。
对比下面几种写法的运行效率

clear
tic
c_array = zeros(1e8,7);
for i = 1:length(c_array)
    c = [sin(i),sin(2*i),sin(3*i),sin(4*i),sin(5*i),sin(6*i),sin(7*i)];
    c_array(i,:) = c;
end
toc

clear
tic
c_array = zeros(1e8,7);
for i = 1:length(c_array)
    c = [sin(i);sin(2*i);sin(3*i);sin(4*i);sin(5*i);sin(6*i);sin(7*i)];
    c_array(i,:) = c';
end
toc

clear
tic
c_array = zeros(7,1e8);
for i = 1:length(c_array)
    c = [sin(i);sin(2*i);sin(3*i);sin(4*i);sin(5*i);sin(6*i);sin(7*i)];
    c_array(:,i) = c;
end
c_array = c_array';
toc

最终运行结果为

  • 第 1 种运行耗时 113.274174 秒
  • 第 2 种运行耗时 157.922921 秒
  • 第 3 种运行耗时 129.031270 秒
    其实这里第 1 种和第 2 种在实际使用时是差不多的。要么就是在一开始就把状态向量都写成行向量,但这样在各个函数内部会很不舒服;要么就是在保存时把列向量转置成行向量。
    是实际使用时采用第 2 种还是第 3 种写法都可以,差别没有那么大。

http://www.kler.cn/news/136103.html

相关文章:

  • [github配置] 远程访问仓库以及问题解决
  • 数据结构02附录01:顺序表考研习题[C++]
  • 前端入门(二)Vue2到Vue3
  • golang学习笔记——要求用户输入一个数字,如果该数字为负数,则进入紧急状态
  • 试用无线调试器PowerDebugger小记
  • ES6有何新特性?(下篇)
  • 【C语法学习】26 - strcat()函数
  • 智能门禁刷脸照片格式gif、bmp,png转换,转换base64
  • 力扣labuladong——一刷day44
  • 网络参考模型与标准协议(一)
  • ORA-00600 【3948】,ORA-00600 【3949】
  • 何时使用Elasticsearch而不是MySql?
  • 虚拟化逻辑架构: 创建KVM中的VM与实现VNC远程登录
  • 键盘控制ROS车运动
  • Linux 串口应用编程
  • 阿里国际站(直通车)
  • 【开题报告】基于SpringBoot的膳食营养健康网站的设计与实现
  • WebGoat通关攻略之 SQL Injection (intro)
  • 持续集成交付CICD:Jenkins Sharedlibrary 共享库
  • Java语言基础第四天
  • centos oracle11g开启归档模式
  • 下一代搜索引擎会什么?
  • 力扣贪心——跳跃游戏I和II
  • 【整顿C盘】pycharm、chrome等软件,缓存移动
  • 记一次线上bug排查-----SpringCloud Gateway组件 请求头accept-encoding导致响应结果乱码
  • pytho你-opencv划痕检测
  • 浅谈无线测温产品在菲律宾某工厂配电项目的应用
  • 手机照片误删解决方法分享
  • Qt中使用QNetworkAccessManager类发送https请求时状态码返回0
  • 【Android】画面卡顿优化列表流畅度六(终篇)