`

【转】浅谈程序优化

 
阅读更多

浅谈程序优化

  当初在学校实验室的时候,常常写一个算法,让程序跑着四处去晃荡一下回来,结果也就出来了。可工作后,算法效率似乎重要多了,毕竟得真枪实弹放到产品中,卖给客户的;很多时候,还要搞到嵌入式设备里实时地跑,这么一来真是压力山大了~~~。这期间,对于程序优化也算略知皮毛,下面就针对这个问题讲讲。

  首先说明一下,这里说的程序优化是指程序效率的优化。一般来说,程序优化主要是以下三个步骤:

  1.算法优化

  2.代码优化

  3.指令优化

 

算法优化 


  算法上的优化是必须首要考虑的,也是最重要的一步。一般我们需要分析算法的时间复杂度,即处理时间与输入数据规模的一个量级关系,一个优秀的算法可以将算法复杂度降低若干量级,那么同样的实现,其平均耗时一般会比其他复杂度高的算法少(这里不代表任意输入都更快)。

  比如说排序算法,快速排序的时间复杂度为O(nlogn),而插入排序的时间复杂度为O(n*n),那么在统计意义下,快速排序会比插入排序快,而且随着输入序列长度n的增加,两者耗时相差会越来越大。但是,假如输入数据本身就已经是升序(或降序),那么实际运行下来,快速排序会更慢。

  因此,实现同样的功能,优先选择时间复杂度低的算法。比如对图像进行二维可分的高斯卷积,图像尺寸为MxN,卷积核尺寸为PxQ,那么

    直接按卷积的定义计算,时间复杂度为O(MNPQ)

    如果使用2个一维卷积计算,则时间复杂度为O(MN(P+Q))

    使用2个一位卷积+FFT来实现,时间复杂度为O(MNlogMN)

    如果采用高斯滤波的递归实现,时间复杂度为O(MN)(参见paper:Recursive implementation of the Gaussian filter,源码在GIMP中有)

  很显然,上面4种算法的效率是逐步提高的。一般情况下,自然会选择最后一种来实现。

  还有一种情况,算法本身比较复杂,其时间复杂度难以降低,而其效率又不满足要求。这个时候就需要自己好好地理解算法,做些修改了。一种是保持算法效果来提升效率,另一种是舍弃部分效果来换取一定的效率,具体做法得根据实际情况操作。

 

代码优化


代码优化一般需要与算法优化同步进行,代码优化主要是涉及到具体的编码技巧。同样的算法与功能,不同的写法也可能让程序效率差异巨大。一般而言,代码优化主要是针对循环结构进行分析处理,目前想到的几条原则是:

  a.避免循环内部的乘(除)法以及冗余计算

    这一原则是能把运算放在循环外的尽量提出去放在外部,循环内部不必要的乘除法可使用加法来替代等。如下面的例子,灰度图像数据存在BYTE Img[MxN]的一个数组中,对其子块  (R1至R2行,C1到C2列)像素灰度求和,简单粗暴的写法是: 

1 int sum = 0; 
2 for(int i = R1; i < R2; i++)
3 {
4     for(int j = C1; j < C2; j++)
5     {
6         sum += Image[i * N + j];
7     }
8 }
View Code

    但另一种写法:

1 int sum = 0;
2 BYTE *pTemp = Image + R1 * N;
3 for(int i = R1; i < R2; i++, pTemp += N)
4 {
5     for(int j = C1; j < C2; j++)
6     {
7         sum += pTemp[j];
8     }
9 }
View Code

    可以分析一下两种写法的运算次数,假设R=R2-R1,C=C2-C1,前面一种写法i++执行了R次,j++和sum+=...这句执行了RC次,则总执行次数为3RC+R次加法,RC次乘法;同  样地可以分析后面一种写法执行了2RC+2R+1次加法,1次乘法。性能孰好孰坏显然可知。

 

  b.避免循环内部有过多依赖和跳转,使cpu能流水起来

    关于CPU流水线技术可google/baidu,循环结构内部计算或逻辑过于复杂,将导致cpu不能流水,那这个循环就相当于拆成了n段重复代码的效率。

    另外ii值是衡量循环结构的一个重要指标,ii值是指执行完1次循环所需的指令数,ii值越小,程序执行耗时越短。下图是关于cpu流水的简单示意图:

    简单而不严谨地说,cpu流水技术可以使得循环在一定程度上并行,即上次循环未完成时即可处理本次循环,这样总耗时自然也会降低。

    先看下面一段代码:

1 for(int i = 0; i < N; i++)
2 {
3     if(i < 100) a[i] += 5;
4     else if(i < 200) a[i] += 10;
5     else a[i] += 20;
6 }
View Code

    这段代码实现的功能很简单,对数组a的不同元素累加一个不同的值,但是在循环内部有3个分支需要每次判断,效率太低,有可能不能流水;可以改写为3个循环,这样循环内部就不  用进行判断,这样虽然代码量增多了,但当数组规模很大(N很大)时,其效率能有相当的优势。改写的代码为:

 1 for(int i = 0; i < 100; i++)
 2 {
 3     a[i] += 5;        
 4 }
 5 for(int i = 100; i < 200; i++)
 6 {
 7     a[i] += 10;        
 8 }
 9 for(int i = 200; i < N; i++)
10 {
11     a[i] += 20;
12 }
View Code

    关于循环内部的依赖,见如下一段程序:

1 for(int i = 0; i < N; i++)
2 {
3     int x = f(a[i]);
4     int y = g(x);
5     int z = h(x,y);
6 }
View Code

    其中f,g,h都是一个函数,可以看到这段代码中x依赖于a[i],y依赖于x,z依赖于xy,每一步计算都需要等前面的都计算完成才能进行,这样对cpu的流水结构也是相当不利的,尽  量避免此类写法。另外C语言中的restrict关键字可以修饰指针变量,即告诉编译器该指针指向的内存只有其自己会修改,这样编译器优化时就可以无所顾忌,但目前VC的编译器似乎不支  持该关键字,而在DSP上,当初使用restrict后,某些循环的效率可提升90%。

 

  c.定点化

    定点化的思想是将浮点运算转换为整型运算,目前在PC上我个人感觉差别还不算大,但在很多性能一般的DSP上,其作用也不可小觑。定点化的做法是将数据乘上一个很大的数后,将  所有运算转换为整数计算。例如某个乘法我只关心小数点后3位,那把数据都乘上10000后,进行整型运算的结果也就满足所需的精度了。

 

  d.以空间换时间

    空间换时间最经典的就是查表法了,某些计算相当耗时,但其自变量的值域是比较有限的,这样的情况可以预先计算好每个自变量对应的函数值,存在一个表格中,每次根据自变量的  值去索引对应的函数值即可。如下例:

 1 //直接计算
 2 for(int i = 0 ; i < N; i++)
 3 {
 4     double z = sin(a[i]);
 5 }
 6 
 7 //查表计算
 8 double aSinTable[360] = {0, ..., 1,...,0,...,-1,...,0};
 9 for(int i = 0 ; i < N; i++)
10 {
11     double z = aSinTable[a[i]];
12 }
View Code

    后面的查表法需要额外耗一个数组double aSinTable[360]的空间,但其运行效率却快了很多很多。

 

  e.预分配内存

     预分配内存主要是针对需要循环处理数据的情况的。比如视频处理,每帧图像的处理都需要一定的缓存,如果每帧申请释放,则势必会降低算法效率,如下所示:

 1 //处理一帧
 2 void Process(BYTE *pimg)
 3 {
 4     malloc
 5     ...
 6     free
 7 }
 8 
 9 //循环处理一个视频
10 for(int i = 0; i < N; i++)
11 {
12     BYTE *pimg = readimage();
13     Process(pimg);
14 }
View Code
 1 //处理一帧
 2 void Process(BYTE *pimg, BYTE *pBuffer)
 3 {
 4     ...
 5 }
 6 
 7 //循环处理一个视频
 8 malloc pBuffer
 9 for(int i = 0; i < N; i++)
10 {
11     BYTE *pimg = readimage();
12     Process(pimg, pBuffer);
13 }
14 free
View Code

    前一段代码在每帧处理都malloc和free,而后一段代码则是有上层传入缓存,这样内部就不需每次申请和释放了。当然上面只是一个简单说明,实际情况会比这复杂得多,但整体思想  是一致的。

 

指令优化


   对于经过前面算法和代码优化的程序,一般其效率已经比较不错了。对于某些特殊要求,还需要进一步降低程序耗时,那么指令优化就该上场了。指令优化一般是使用特定的指令集,可快速实现某些运算,同时指令优化的另一个核心思想是打包运算。目前PC上intel指令集有MMX,SSE和SSE2/3/4等,DSP则需要跟具体的型号相关,不同型号支持不同的指令集。intel指令集需要intel编译器才能编译,安装icc后,其中有帮助文档,有所有指令的详细说明。

  例如MMX里的指令 __m64 _mm_add_pi8(__m64 m1, __m64 m2),是将m1和m2中8个8bit的数对应相加,结果就存在返回值对应的比特段中。假设2个N数组相加,一般需要执行N个加法指令,但使用上述指令只需执行N/8个指令,因为其1个指令能处理8个数据。

  实现求2个BYTE数组的均值,即z[i]=(x[i]+y[i])/2,直接求均值和使用MMX指令实现2种方法如下程序所示:

 1 #define N 800
 2 BYTE x[N],Y[N], Z[N];
 3 inital x,y;...
 4 //直接求均值
 5 for(int i = 0; i < N; i++)
 6 {
 7     z[i] = (x[i] + y[i]) >> 1;
 8 }
 9 
10 //使用MMX指令求均值,这里N为8的整数倍,不考虑剩余数据处理
11 __m64 m64X, m64Y, m64Z;
12 for(int i = 0; i < N; i+=8)
13 {
14     m64X = *(__m64 *)(x + i);
15     m64Y = *(__m64 *)(y + i);
16     m64Z = _mm_avg_pu8(m64X, m64Y);
17     *(__m64 *)(x + i) = m64Z;
18 }
View Code

  使用指令优化需要注意的问题有:

  a.关于值域,比如2个8bit数相加,其值可能会溢出;若能保证其不溢出,则可使用一次处理8个数据,否则,必须降低性能,使用其他指令一次处理4个数据了;

  b.剩余数据,使用打包处理的数据一般都是4、8或16的整数倍,若待处理数据长度不是其单次处理数据个数的整数倍,剩余数据需单独处理;

 

补充——如何定位程序热点


  程序热点是指程序中最耗时的部分,一般程序优化工作都是优先去优化热点部分,那么如何来定位程序热点呢?

  一般而言,主要有2种方法,一种是通过观察与分析,通过分析算法,自然能知道程序热点;另一方面,观察代码结构,一般具有最大循环的地方就是热点,这也是前面那些优化手段都针对循环结构的原因。

  另一种方法就是利用工具来找程序热点。x86下可以使用vtune来定位热点,DSP下可使用ccs的profile功能定位出耗时的函数,更近一步地,通过查看编译保留的asm文件,可具体分析每个循环结构情况,了解到该循环是否能流水,循环ii值,以及制约循环ii值是由于变量的依赖还是运算量等详细信息,从而进行有针对性的优化。由于Vtune刚给卸掉,没法截图;下图是CCS编译生成的一个asm文件中一个循环的截图:

    最后提一点,某些代码使用Intel编译器编译可以vc编译器编译出的程序快很多,我遇到过最快的可相差10倍。对于gcc编译后的效率,目前还没测试过。

分享到:
评论

相关推荐

    浅谈性能优化方法和技巧.pdf

    如果一个程序的热点已经完全填充了整个缓存,那么再从缓存角度考虑优化就是白费力气了。 缓存线大小是缓存的基本单位,CPU从内存load数据是一次一个缓存线;往内存里面写也是一次一个缓存线,所以一个缓存线里面的...

    浅谈程序设计竞赛的算法知识-罗勇军1

    罗勇军所著的“浅谈程序设计竞赛的算法知识”详细阐述了竞赛中所需掌握的各种算法知识点,并强调了这些知识对于竞赛乃至职业发展的重要性。 首先,作者罗勇军明确指出算法知识是程序设计竞赛的核心,围绕着Ad Hoc、...

    浅谈ORACLE数据库的性能优化.pdf

    Oracle内存分为系统全局区(SGA)、程序全局区(PGA)、排序区(SORT AREA)、大池(LARGE POOL)和Java语言区(JAVA POOL)。SGA是最关键的部分,它包含数据缓冲区、日志缓冲区和共享池。数据缓冲区存储从数据文件...

    浅谈Oracle优化排序的操作

    ### 浅谈Oracle优化排序的操作 #### 概念与机制 在探讨Oracle排序操作的优化之前,我们首先需要理解Oracle数据库中的排序是如何发生的以及它所依赖的资源。Oracle的排序操作通常发生在以下几种情况下:创建索引、...

    浅谈Java程序的性能优化

    【Java程序性能优化】在Java程序开发中,性能优化是一个重要的环节,因为它直接影响到程序运行效率和用户体验。本文将从基础优化和GUI设计时的优化两个方面探讨如何提升Java程序的性能。 一、基础优化 1. **避免...

    浅谈App优化浅谈App优化

    在当前移动互联网时代,应用程序(App)已成为人们日常生活中不可或缺的一部分。随着市场竞争的日益激烈,如何提升App的性能与用户体验成为了开发者们关注的重点。本文将围绕App优化的关键方面进行探讨,包括App瘦身...

    浅谈Codewarrior局部优化技巧

    首先,程序优化的主要目的是解决硬件平台资源限制,特别是处理速度(Speed)和存储空间大小(Size)的限制。例如,在ARM平台上运行8051微控制器代码,由于资源冗余,通常不需要考虑优化。然而,复杂应用中往往会出现...

    浅谈微信小程序第三方开发及应用探析.pdf

    6. 专业指导:开发者在小程序开发过程中需要遵循的规范、性能优化的技巧、接口调用的最佳实践等都可能包含在专业指导的内容中。 虽然提供的文档内容片段难以理解,但根据已有的信息,可以推测文档将为读者提供有关...

    浅谈_Python_程序和_C_程序的整合

    随着软件开发领域的不断发展,开发者面临着越来越多的挑战,如提高开发效率、优化程序性能等。Python作为一种高级编程语言,以其简洁明了的语法和丰富的第三方库而广受欢迎。然而,Python在执行速度方面相对较慢,...

    浅谈Matlab优化工具箱在机械优化设计中的应用.pdf

    4. 在输入参数确定后,调整优化函数中的程序。这通常包括对算法初始参数的设定和优化过程的调整。 5. 根据优化过程中提示的具体信息,对参数进行调整和优化,最终得到问题的最优解。 在优化工具箱的使用中,拟牛顿...

    浅谈单片机应用程序架构

    "浅谈单片机应用程序架构" 单片机应用程序架构是指在单片机系统中对程序的架构设计和实现。本文档将总结归纳单片机应用程序架构的相关知识点,并对其进行分类和比较。 一、简单的前后台顺序执行程序 这种方法是...

    浅谈单片机程序架构.pdf

    浅谈单片机程序架构 单片机程序架构对于单片机程序来说大家都不陌生,但是真正使用架构,考虑架构的恐怕并不多。随着程序开发的不断增多,本人觉得架构是非常必要的。本文将浅谈单片机程序架构,讨论怎样架构单片机...

    浅谈微信小程序在图书馆的使用现状.docx

    同时,加强与用户之间的互动,收集反馈,不断优化服务。此外,图书馆还应联合微信平台,开展有针对性的推广活动,提高小程序的知名度和使用率。 总的来说,微信小程序为图书馆提供了新的服务模式和机遇,通过合理...

    浅谈如何优化SQL语句提高数据库系统效率.pdf

    本篇文档题为“浅谈如何优化SQL语句提高数据库系统效率”,旨在探讨在数据库系统中如何通过优化SQL语句来提升整体性能。文档中提到了多种SQL优化的策略和方法,涵盖了查询优化、索引使用、查询语句的设计原则等方面...

    浅谈SQL语句的优化对应用程序性能的影响.pdf

    据统计,大约60%的系统性能提升来源于应用程序优化,其中SQL语句的优化占据了重要的比重。SQL是数据库操作的核心,任何应用程序的数据交互最终都会转化为SQL对数据库的指令,因此SQL执行效率直接影响到应用程序的...

    浅谈对《MATLAB程序设计》课程基础教学内容的思考.pdf

    本模块重视程序设计的逻辑思维和实现技巧,帮助学生掌握程序结构的设计、调试和优化。 在教学实践中,李豪讲师提出提高学生程序设计能力的途径,其中包括对基础概念的准确理解、对重难点内容的深入讲解以及鼓励学生...

    浅谈SQL Server数据库应用程序的性能优化.pdf

    SQL Server数据库应用程序性能优化涉及多个方面,从基础的数据库设计到存储过程的应用,再到事务和批处理操作,每一个环节都对最终性能产生着决定性的影响。 首先,数据库设计阶段的性能优化是至关重要的。规范化...

    浅谈VB程序的调试和错误处理.

    《浅谈VB程序的调试和错误处理》这篇文章深入探讨了Visual Basic(以下简称VB)程序设计中常见的错误类型及其处理方法,对于提升VB程序的开发效率和质量具有重要意义。作者陈玉泉和陈汉松,分别来自常州工学院计算机...

    浅谈数据库系统优化.docx

    总结来说,数据库系统的优化是一项系统工程,涵盖数据库设计、应用程序优化、内存管理和磁盘I/O调整等多个层面。在SQL Server环境下,理解并运用不同类型的优化器有助于提升数据库性能,从而提高整体系统效率。持续...

Global site tag (gtag.js) - Google Analytics