编码中的一些优化技巧
来源:互联网 发布:vc6.0连接mysql数据库 编辑:程序博客网 时间:2024/05/29 04:47
编码中的一些优化技巧
减少指令数
1.降低数据精度
小数点后面位数越多,精度越大。100.11比100.1更加精确。越是精确的数据,所用的位数越多。运算时间越长。浮点数有双精度和单精度之分,单精度浮点数占32bit,双精度浮点数占64bit,处理双精度数据自然要比单精度数据慢。
在C语言中,fabsf()是计算单精度浮点数绝对值的函数,而fabs()是计算双精度浮点数绝对值的函数。如果数据是单精度浮点数,使用fabsf()要比fabs()快。如果一个数据能够使用单精度表示,就不需要使用双精度表示。
2.减少函数的调用
函数调用会带来额外的开销,除了引起跳转外,还会产生额外的指令。函数调用是这样的,要进行参数压栈出栈、寄存器的保存、指令跳转等多个步骤,如果程序的性能要求较高,就可以把较小的函数直接转换为代码。
1)将函数直接写出语句
eg: int min(int a, int b){return a<b? a:b;}c =min(a,b)
直接改写为:
c = a< b? a:b;
2) 将小函数写出宏
#defineMIN(a,b) ((a) < (b) ? (a) : (b))c =MIN(a,b);
3) 将函数声明为内联函数。
如果嫌宏太麻烦,可以将函数声明为inline函数,编译器将会自动用函数体覆盖函数调用。
inline int min(int a, int b){ return a < b?a :b;}c = min(a,b); //编译器将优化为 c = a<b?a:b;
3.空间换时间
Fibonacci序列实现的例子中:
eg1:int f(int n){ if(n <= 1) return 1;else return f(n-1)+f(n-2);}int main(){ intresult; int i; for(i= 0;i < 40;i++) { result= f(i); printf(“%d\n”,result); }}eg2:int arr[40];int f(int i){ intresult; if(n<=1) return1; else{ resultarr[n-1]+arr[n-2]);}arr[n] = result;return result;}int main(){ intresult; int i; for(i= 0;i < 40;i++) { result= f(i); printf(“%d\n”,result); }}
eg1在电脑上耗时21秒。而eg2耗时不到1秒。eg2使用了数组将F的值缓存起来,这样计算F(n) = F(n-1)+F(n-2)的时候不在执行递归,直接从数组中取值即可。典型的空间换时间策略。
减少处理器不擅长的操作
单周期指令是处理器最喜欢的,不仅执行时间短,而且有利于流水线。加、减、逻辑运算都是单周期指令,乘、除、分支指令、浮点指令、内存存取指令等,都需要较多的时钟周期。编程的时候,尽可能的少用执行周期长的指令。
1.少用乘法
定点乘法在DSP中需要两个Cycle,而移位操作只需要一个Cycle。如果一个数是乘以2的N次方,就可以用移位代替乘法。
len = len *4;
可以改写为:
len = len<< 2;
2.少用除法和取余
除法和取余操作,将消耗大量时间,很多处理器没有相应的指令,是通过软件实现的。所以应该尽量少用。如果是除以一个常识,eg:
f = f /5.0
可以将它改写为乘法操作:
#define cof 1.0/5
f = f * cof
假如a数据范围为[128,511], b数据范围为18位有效位,当计算b/a的结果时,我们可以把a的数据范围映射到一个整数范围(范围可大可小?),然后与b相乘,然后再移位,使得最后的数据位与其原本b/a的数据位相等。比如我们可以把a通过tmp = 65535/(a-128),映射到[257,511]。即最后的结果把除法运算转换为乘法运算,b/a = (b*tmp) >> 16,右移16bit确保最后的数据位与原来的b/a的结果数据位相同。
3.在精度允许的情况下,可以将浮点数定点化
浮点指令要比定点指令慢很多,功耗也大。在精度不那么高的情况下,就可以将浮点数定点化。用定点指令代替浮点指令。
eg:alpha混合实例
利用两张半透明的图像混合实现alpha混合效果。混合后的图像中每个像素颜色值为:
Pixel_C =(int)(Pixel_A* alpha + Pixel_B*(1-alpha));
alpha为透明度,介于0到1之间的小数。这条语句是浮点运算,如果每个像素都经过这样的运算,是相当耗时的。其实可以将alpha定点为0到32之间的一个整数。改写后为:
Pixel_C =(int)(Pixel_A* alpha + Pixel_B*(32-alpha)+16)>>5;
在图像处理中,RGB转换YUV中常用到这种技术。//BT 601Y = ((((306* R +601*G + 117*B)>>10)-Y_Shift)*Y_Contrast)>>6;Y = CLIP(Y, 0, 1023);U= (-173* R -339*G + 512*B+512)>>10 ;U = CLIP(U, -512, 511);V= (512 * R - 428*G -84*B+512)>>10;V = CLIP(V, -512,511); 本来RGB转YUV时,应该是RGB三通道的值分别乘以相应的小数。在以上的算法中,把相应的小数乘以了1024,变成定点整数。执行时间要大幅度减少。由于ARM是精简指令集的处理器,没有专门计算浮点的硬件支持,它是在浮点模拟器中进行,因此,速度较慢。
4.尽量减少分支
现在的处理器都是流水线结构,if 和switch语句会带来跳转,而跳转将打乱流水线的正常执行,影响程序的执行效率。
下面的代码把奇数赋一个值,把偶数赋一个值。
for(i=0;i < 100;i++){ if(i %2 == 0) a[i]= x; else a[i]= y;}
改写如下形式会更好些:
for(i=0;i< 100;i++){ a[i] = x; a[i+1]= y;}
5.将最可能进入的分支放到if中,而不是else中
Intel处理器有预测分支单元,第一次进入一个分支的时候,由于没有历史信息可供参考,是否跳转取决于Static Predictor(静态预测器)的预测策略。通常静态预测器的预测策略是:向下跳转预测为不跳转,向上跳转预测为跳转。根据此特性,if语句也是需要按照这种方式去编码。
int a = -5;int b = 0;if(a > 0) b= 1;else b= -2;
优化内存访问
CPU的执行速度越来越快,但是内存的访问速度却增长缓慢。所有数据和程序必须放入内存才可以工作。CPU计算的时候,需要从内存中读取相应的数据和程序,但是内存的访问速度太慢,严重影响了计算机的执行效率。为了弥补内存速度低下的问题,处理器内部会放置一些SRAM做Cache(缓存),来提高处理器访问程序和数据的速度。Cache作为内核和内存的桥梁如下图所示:
Cache有两个重要的性质:
1)时间局限性(Temporal Locality):如果某个数据被访问,那么不久的将来它很可能再次被访问。最典型的例子就是循环。循环体代码被处理器重复执行,知道循环结束。如果将循环体代码放到Cache中,那么只需要第一次读取改代码需要耗时,以后这些代码每次都能够被内核快速访问,节约了时间。
2)空间局限性(Spatial Locality):如果某项数据被访问,那么与它相邻的数据很可能很快就被访问。典型的例子就是数组。我们一次将数组中的多个数据从内存复制到Cache中,虽然访问第一个元素需要花费点时间,但是后续的元素访问就很快了。这是Cache在空间局限性上的应用。
1.少用数组,少用指针
由于大块数据将放到存储器中,简单局部数据将放到寄存器中,因此,尽可能的少用数组和指针,多用简单局部变量。
下面这段代码,需要4次内存访问
c = a [i]*b[i];
d = a[i]+b[i];
如果改写如下形式,就只需要两次内存访问
x = a[i];
y = b[i];
c = x*y;
d = x+y;
2.少用全局变量
全局变量由于需要被多个模块使用,不会放到寄存器中,局部变量才能被放到寄存器中。应该尽可能的少使用全局变量。
int x;int fun_a(){ inty,z; y= x; z= x+1; …….}
最好改写为:
int xint fun_a(){ inty,z,temp; temp= x; y= temp; z= temp+1; ……}
3.数据对齐访问
对于32bit处理器,一个int型变量i在内存中占据2、3、4、5这四个byte位置
内存访问这个数据的时候,会从0开始的4个byte读入到寄存器A中,再将从4开始的4个byte读入到寄存器B中,然后再将有效数据拼成一个int数据,放到寄存器C中。这种方式访问效率低下。如果变量i存储的位置在从0开始的4个byte处,那么i就可以一次读入到寄存器中。这就是字节对齐与不对齐的差别。对于2byte的变量,它的地址应该为2的整数倍,同理,对于4byte和8字节的变量,它们的起始地址应该分别为4的整数倍和8的整数倍,这样访问速率才会高。
4.程序和数据访问符合Cache的时间和空间局部特性
为了使Cache访问效率最高,程序和数据的组织,也应该符合两个特性。典型的例子就是二位数组的访问。
如果a[i][j]在Cache中,那么a[i][j+1]就很可能在Cache中,而a[i+1][j]则不一定。如果代码这样写,效率并不高。
for( j = 0;j <500;j++){ for(I = 0; i< 500;i++) { sum += a[i][j]; }}
改写代码如下后,效率将提高很多。
for( i= 0; i <500; i ++){ for(j= 0; j< 500;j++) { sum += a[i][j]; }}
资料:
《大话处理器》---处理器基础知识读本 万木杨著
- 编码中的一些优化技巧
- 浅谈C\C++代码优化中的一些小技巧
- 浅谈C\C++代码优化中的一些小技巧
- 浅谈C\C++代码优化中的一些小技巧
- android 一些优化技巧
- Java编码优化10技巧
- PHP编码优化加速技巧
- 特征编码的一些技巧
- Sqlserver中的一些技巧
- 一些编程 中的技巧
- JDBC 中的一些技巧
- C#中的一些技巧
- Sqlserver中的一些技巧
- Word中的一些技巧
- ubuntu 中的一些技巧
- css中的一些技巧
- Pycharm中的一些技巧
- Jquery中的一些技巧
- 判断一个整数转换成二进制后1的个数
- [安卓基础]学习第五天
- 1019. 数字黑洞 (20)
- 1020. 月饼 (25)
- python
- 编码中的一些优化技巧
- 搜索 I
- 1021. 个位数统计 (15)
- 1022. D进制的A+B (20)
- 一个简单函数的是如何运行的
- peerconnection_client的MainWnd分析(一)
- web服务器之iis,apache,tomcat三者之间的比较
- 1023. 组个最小数 (20)
- win8如何快速关闭135\445端口,预防onion永恒之蓝病毒