试谈MATLAB代码性优化及编程策略

相信用过Matlab的朋友,特别是需要处理大数据(matrix/cell/structure…)的朋友,必然深深地体会到Matlab的“慢”与“肥”。不仅脚本执行效率不高(不是不高,是Matlab简直就是性能低下),就连Initialize都要半天。还有经常性清脆地“滴”一声之后,出现红色的提示“Out of memory”。还有对多线程的支持不好、CPU无法满载、打开*.mat数据大小限制等等问题,总之一言难尽。

在写了不多,但是也不算少的matlab程序之后,特别是大量跟数据搏斗的程序,我开始渐渐有些体会。这里提出一些方法,是一些小技巧,是小人物、破机器跟贱数据搏斗之后,大难不死的一些心得,也有一些是引用别人的心得。趁着假期将它们逐渐整理出来,或许不是很全,有一些曾经注意过的地方有遗漏,所以或许还会有续集。

Matlab性能优化,通过优化代码,通常主要优化的对象是两个,一是效率、速度,二是系统资源的占用

  • 【速度优化】慎用for、while循环,多用矩阵运算(Vectorizing your code)。
    习惯于c/c++等非高级语言的朋友在开始接触Matlab的时候通常会忽略很重要的一点,就是matlab它自身带有非常强大的矩阵运算(Vectorized function),不习惯或者不知晓这种方式的朋友通常会选择使用for等循环方式来对矩阵中的单元格进行遍历处理。其实for循环在Matlab当中是非常慢的,尽管有文章声称Matlab已经改进一些Matlab基础方法(basic components)的速度,但是在2009b版本仍然没有看到太大的改善。所以在需要对矩阵进行操作时候,最好灵活应用矩阵运算方法。
    矩阵运算方法是一个不大不小的学问,其实我觉得看书的能学到的技巧微乎其微,关键还是要靠自己平时的积累,以下列举一个遍历矩阵的实例:需要遍历一个30×30的矩阵A,找出等于7的项,并对其执行相应的操作。
    position = find(A==9);
    上面的命令返回一个列向量指出A中等于7的位置
    然后我们对A进行操作,对等于7的地方赋予717,对于第一种用find命令得到的position可以这样写:
    A(position)=717;
    又或者,如果position变量你只用到一次的话,其实可以省下一个position变量,直接这样写:
    A(find(A==7))=717;
    再举一例吧,这次假设我们一定非得要用for循环来对矩阵进行遍历,这时候我们只需要一个for循环就可以实现对矩阵实行遍历:
    for i=1:(size(A,1)*size(A,2)) Do_Something_Here(); end
    因为Matlab中矩阵的格子有一个单一的索引号从第一列开始从上到下数完全部行之后再移到下一列,因此只一个for循环就ok了。
    忍不住再举一例,也算是一个技巧。某些情况下我们还是需要用两个for循环来对矩阵进行操作,这时候我们可以把对列(column)的循环写到外面,把对行(row)的循环写到里面。因为这样会快一倍左右。至于为什么会快一点呢?我懒得翻译了,直接给出传说中的答案:
    Modern CPUs use a fast cache to reduce the average time taken to access main memory. Your code achieves maximum cache efficiency when it traverses monotonically increasing memory locations. Because MATLAB stores matrix columns in monotonically increasing memory locations, processing data column-wise results in maximum cache efficiency.
    最后列出一些常用的矩阵运算时候经常用到的命令:
    all end logical repmat squeeze
    any find ndgrid reshape sub2ind
    cumsum ind2sub permute shiftdim sum
    diff ipermute prod sort -

  • 【速度优化】先定义矩阵大小,防止矩阵在程序中不断变换大小。
    其实Matlab中不一定要先定义变量,因为Matlab中的变量是非常灵活的。但是这种灵活性不代表Matlab就会智能到知晓一切,比如你的变量的大小。于是很多人都可能会忽略一点就是,在循环中让你的矩阵变量不定型地变换大小是非常致命的(总要自己试过才知道是多致命)。而唯一的终极解决的方法就是不要那么懒(大忌),在循环之前先计算好矩阵的大小,然后预先给你的矩阵指定大小(用zeros或者ones,随你)。如此,速度可能会是1000倍速以上,不信你试试···
  • 【速度优化】function比script更快
    其实这个我也是在网上看到的,说是运行脚本会把不必要的变量加载到内存,而函数只加载一次。我自己并没有太多感觉。但是function的确比script好的一个地方就是,function结束以后,除了在function头定义的输出变量之外,并没有其他任何function所涉及到的变量会被保留,这个首先就是不会造成宝贵的内存空间的浪费,再就是debug的时候不会头晕晕将变量都搞糊涂了(除非你有非常统一的变量命名规则,我就不信你永远都不会用i、j之类的临时循环计数变量)。
  • 【速度优化】关于Matlab中的I/O
    先说说数据文件格式的选择。当我们保存数据、再读取数据的时候,我们应该首先考虑mat类型的数据文件格式。首先mat类型的通用性比较好,只要Include一个mat.h的头文件,在c程序中也可以实现读取mat文件,而不必担心它的格式不通用的问题。再者,由mat文件读入数据后,可以轻易地用Matlab自带函数(如csvwrite、xlswrite等)将其转换成其他通用数据格式诸如csv、xls。还有一个考虑到的地方将会在下面提到,是关于Matlab中I/O命令的执行效率问题。
    在Matlab中,load/save命令是加载mat格式文件用到的两个命令,其已经对读取文件进行过优化,效率要比fopen之类的要高。所以在I/O命令的选择上面,除非碰到.txt/.dat之类的非通用格式的文件没办法外,我们一般都建议使用load/save来加载mat文件。
    最后还有非常重要的地方要提一下,就是在进行对硬盘中文件I/O操作的时候,千万不要再开其他程序对那些文件或者其所在的文件夹进行读取或者写入。首先,两个程序同时对一个文件夹操作会造成文件检索、文件读写缓慢,甚至能让Matlab假死的情况出现。平时我用Linux系统的时候更是感觉文件系统不及windows快速,打开多文件的文件夹速度明显不如windows,不知道是GNOME桌面还是Nautilus的问题。还有一点,就是一个程序在那个文件夹里面增加/减少文件,会导致另外一个程序的I/O错误,比如说这边删掉了一个文件,那边却正在读那个文件,当然就是crash啦。
  • 【速度优化】关于不必要的变量(Unnecessary Variables)

有人可能会认为,不必要的变量只是会占用内存空间而已(虽然已经够致命了)。其实不然,多余的变量还会影响速度。试用下面两个代码段作比较:

    %% segment 1.
    tic;
    x=myfun(x);
    t_seg1=toc;
    disp(t_seg1);
    %% segment 2.
    tic;
    y=myfun(x);
    t_seg2=toc;
    disp(t_seg2);
    结果是t_seg2比t_seg1大了两个数量级,两个代码的区别是seg1比sge2少创建一个不必要的变量y。至于为什么减少不必要的变量可以使速度变快呢?原因很简单,还是那个,你创建一个新变量,必须在内存中给它allocate一个空间,必须赋值给它,这些都是需要时间的,虽然处理小数据的时候,这些并没有什么太大分别,但是当你需要与大数据、小内存搏斗时,你就要开始考虑这些细节了。

还有下一页… 好臭好长…

Pages: 1 2 3

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>