C语言程序代码优化

我认为一个好的用于科学计算的程序代码应该:算法漂亮精妙,程序简洁易懂,运算快速,节省内存。这里有的地方是矛盾的,比如简洁vs易懂,时间vs空间,找个平衡吧。目前来看时间要比空间宝贵一些。写程序分几步:选择最妙的算法;规划最优的流程;规划数据结构、函数;编码实现。以下是查找网上资料后的总结。 


一、好的方法、算法和数据结构是程序优化的根本,选择最好的算法永远是王道。

二、规划流程时几个不依赖于编译器的tips:

1、减少运循环体内运算量:

(a),查表:提前列表,循环内查表。

(b),提取循环的公共子式到循环外计算。

(c),将循环体展开以减少循环的判断过程。

2、判断式合理排列conditions减少判断次数:

(a),根据发生频率排列switch语句的case,或者if语句的条件式。

(b),将一些低概率条件合并及嵌套判断。

(c),将多重条件嵌套判断。

3、合理组织循环和判断的嵌套

(a),将值不变的条件式放在循环的外面。


三、C语言设计数据结构的tips.

1,使用尽量小的数据结构。如char好于int好于float。

2,使用便于运算的数据结构。

3,数据合理布局

(a)结构体数据成员按类型长度排序。

(b)把结构体填充成最长类型长度的整数倍。

4,变量名短好于长

5,同时声明变量好于分别声明变量


四,C语言数据操作的tips。

1,使用指针。

2,尽量使用常量。

3,常用变量设置为寄存器变量。

4,初始化好于赋值。

5,减少文件读取操作


五,C语言数据运算强度的优化,即使用快的运算代替慢的运算。

1,使用位运算

2,用a*a代替pow(a,2.0)。

3,减少整数除法,如用i/(j*k)代替i/j/k。


六,C语言函数优化。

1,函数用inline代替外部调用(但会增加程序长度)。

2,定义函数原型,便于编译器优化。

3,不定义不使用的返回值。

4,本地函数声明为静态。

http://blog.sciencenet.cn/blog-1005104-727037.html 

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

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列)像素灰度求和,简单粗暴的写法是:

但另一种写法:

可以分析一下两种写法的运算次数,假设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流水技术可以使得循环在一定程度上并行,即上次循环未完成时即可处理本次循环,这样总耗时自然也会降低。

先看下面一段代码:

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

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

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

 

c.定点化

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

 

d.以空间换时间

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

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

 

e.预分配内存

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

前一段代码在每帧处理都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种方法如下程序所示:

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

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编译后的效率,目前还没测试过。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458644.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信支付不回调支付成功的方法,这是为什么

如果你是Xcode7.2,或者IOS9.2的话,可能会遇见在微信客户端操作返回程序之后不能执行微信的onResp回调方法的问题,就是因为一下这两个方法被废弃掉了,所以我的新demo替换了一个新的方法在下面。就完美解决这个问题了(并…

如何在苹果官网下载旧版本的Xcode 方法

1 在百度里输入“苹果开发者中心“,进入以下页面。点击页面中的“Member Center" 2 出现登录界面。这是需要苹果开发者帐号的,没有帐号的可以选择“Create Apple ID”进行注册。已经注册的选择“Sign In"登录 3 页面跳转后,选择…

屏幕尺寸 分辨率

1、分辨率 分辨率又称显示分辨率、屏幕分辨率 确定手机屏幕上显示多少信息的设置,以水平和垂直像素来衡量 6 750 *1334 像素 5s 640 * 1136 像素 2、屏幕尺寸 屏幕大小的物理尺寸,以屏幕对角线长度衡量 单位:英寸 1英寸2.54厘米 6 4.7英…

程序代码优化2

程序进行优化,通常是指优化程序代码或程序执行速度。优化代码和优化速度实际上是一个予盾的统一,一般是优化了代码的尺寸,就会带来执行时间的增加,如果优化了程序的执行速度,通常会带来代码增加的副作用,很…

【转】android多分辨率适配

前一阶段开发android项目,由于客户要求进行多分辨率适配,能够支持国内主流的分辨率手机。因此经过了几次开发走了很多弯路,目前刚刚领略了android多分辨率适配的一些方法。 先介绍一下所走的弯路,由于android的布局文件存放在res的…

TCP/IP SOCKET HTTP及HTTPS之间的关系

GET跟POST的区别: get只能传送128K的数据 而post是无限制的 post提交是不在会IE上带上参数 就算你加密了别人也会解密 一般比较重要的数据通过post 传,因为get是别人可以改参数值的 别人乱写参数,你的异常报个不停 网络七层由下往上分别为物理…

静态链接与动态链接的区别

动态链接库、静态库、import库区别 动态链接库(Dynamic Linked Library): Windows为应用程序提供了丰富的函数调用,这些函数调用都包含在动态链接库中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函…

Java线程池介绍

根据摩尔定律(Moore’s law),集成电路晶体管的数量差不多每两年就会翻一倍。但是晶体管数量指数级的增长不一定会导致 CPU 性能的指数级增长。处理器制造商花了很多年来提高时钟频率和指令并行。在新一代的处理器上,单线程程序的执…

curl -L get.rvm.io | bash -s stable报错:连接不上服务器

1、安装cocoa pods时, ERROR: Error installing cocoa: activesupport requires Ruby version > 2.2.2. 这个错误是说:rvm的版本过低,需要升级一下版本 2、升级rvm版本的时候,报标题的错误解决办法如下 将上面的命令行改成&a…

C语言中#define的用法(转)

转自&#xff1a;http://www.dingge.com/main/article.asp?id10 今天整理了一些#define的用法&#xff0c;与大家共享&#xff01; 1.简单的define定义 #define MAXTIME 1000 一个简单的MAXTIME就定义好了&#xff0c;它代表1000&#xff0c;如果在程序里面写 if(i<MAXTIM…

cocoa pods的安装与我遇到的问题

1.打开终端 终端输入 ruby -v 查看ruby的版本 打印代码&#xff1a; ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15] 2. 更换ruby镜像 终端输入如下命令&#xff08;把Ruby镜像指向taobao&#xff0c;避免被墙&#xff0c;你懂得&#xff09; a.移…

Node 连接Mysql并进行增删改查

NPM: NPM的全称是Node Package Manager&#xff0c;类似于ruby的gem&#xff0c;Python的PyPL、setuptools&#xff0c;PHP的pear&#xff0c;是Nodejs中的包管理器。Nodejs自身提供了基本的模块。但是在这些基本模块上开发实际应用需要较多的工作。NPM上已经有近万个Nodejs库或…

C++/C 宏定义(define)中# ## 的含义(转)

参考&#xff1a;http://www.cnblogs.com/little-ant/p/3463080.html http://hi.baidu.com/kiraversace/item/1148ee057147981a4ac4a3e9 C/C 宏定义&#xff08;define&#xff09;中# ## 的含义 define 中的# ## 一般是用来拼接字符串的&#xff0c;但是实际使用过程中&#x…

Implicit declaration of function 'NSFileTypeForHFSTypeCode' is invalid in C99

一般出现该问题是因为通过C调用了unix/linux 底层接口&#xff0c;所以需要调整c语言的编译选项&#xff0c;设置方法见下图&#xff1a;(根据实际情况选择相应的编译选项)

C++里的花括号{},块,作用域

{ } 里的内容是一个“块”。单独的{ }在执行顺序上没有改变&#xff0c;仍然是顺序执行&#xff0c;不同的是标识符的作用域限定 。#include <iostream>#include <string>using namespace std;class tcalss{string Name;public:tcalss(string name){Namename;cout&…

iOS关于armv7,armv7s,arm64,i386,x86_64等问题

iOS测试分为模拟器测试和真机测试&#xff0c;处理器分为32位处理器&#xff0c;和64位处理器&#xff0c; 模拟器32位处理器测试需要i386架构&#xff0c;&#xff08;iphone5,iphone5s以下的模拟器&#xff09; 模拟器64位处理器测试需要x86_64架构&#xff0c;(iphone6以上的…

DebugView 调试入门

参考链接&#xff1a;http://blog.csdn.net/jiankunking/article/details/44984487 软件下载地址&#xff1a;点击打开链接 debugview 可以捕获程序中由TRACE(debug版本)和OutputDebugString输出的信息。支持Debug、Release模式编译的程序&#xff08;即该软件捕获的是exe直接运…

AJAX只支持字符类数据返回,不支持文件下载

如题转载于:https://www.cnblogs.com/caicaizi/p/5000363.html

Xcode中指令集相关选项

Xcode中指令集相关选项&#xff08;Build Setting中&#xff09; &#xff08;1&#xff09;Architectures Space-separated list of identifiers. Specifies the architectures (ABIs, processor models) to which the binary is targeted. When this build setting specifies…

DebugView使用笔记

1. 什么是DebugView? 它是Sysinternals公司的系列调试工具。可以捕获程序中由TRACE()和OutputDebugString输出的信息。 2. C需要完成哪些工作呢&#xff1f; 将打印的信息用OutputDebugString输出&#xff0c;示例&#xff1a; [cpp] view plaincopy #include "stdio.h&q…