-
应用级别
1.编译器选项
许多编译器具有自己的优化方案,也是最容易最稳定的实现方法
2.调用高性能库
如:BLAS FFTW
3.去掉全局变量
4.受限指针
多个指针指向同一个内存地址或指向的内存地址有重叠,它会阻碍编译器对程序进行指令重排、表达式移除等优化。restrict标识符,指定受限指针
5.条件编译
-
算法级别
1.索引顺序
访问多维数据时的局部性直接与各维数据在内存中存放的先后顺序有关。如C语言中数据是以行为主序存放的,在计算时尽量按行访问数据。
for( int i = 0; i<M; i++){
float ret=0.0f;
for(int j =0 ;j<N;j++){
ret+=a[i][j];
}
r[i]=ret;
}
2.缓存分块
3.软件预取
数据被使用前,投机的加载到缓存中
4.查表法
会较少精度,实际项目中将查表法和线性插值结合减少精度的降低
-
函数级别
1.函数调用参数
如果函数的参数是大结构体或类,应当通过传指针或引用减少调用时复制和返回时的销毁开销
2.内联小函数
能够消除函数调用的开销,并提供更多的指令级并行,表达式移除等优化机会。建议少于10行的函数inline
-
循环级别
1.循环展开
展开循环不但减少了每次的判断数量和循环变量改变的次数,更能增加流水线执行的性能
float sum=0.0f;
for(int i=0;i<num;i++){
sum+= a[i];
}
优化后:
float sum=0.0f,sum1=0.0f,sum2=0.0f,sum3=0.0f;
for(int i = 0;i< num;i+=4){
sum1 +=a[i];
sum2 +=a[i+1];
sum3 +=a[i+2];
sum +=a[i+3];
}
sum +=sum1+sum2+sum3;
对于二层循环来说,建议优先展开外层循环,但不是一个普适的准则。
需要注意!!!留意处理末尾的数据
2.循环累积
循环累积主要和循环展开同时使用,减少寄存器的使用量的同时保证平行度。
float sum=0.0f,sum1=0.0f,sum2=0.0f;
for(int i = 0;i< num;i+=6){
sum1 +=a[i]+a[i+1];
sum +=a[i+2]+a[i+3];
sum2 +=a[i+4]+a[i+5];
}
sum +=sum1+sum2;
直接展开6次需要6个临时变量,现在只需要3个。
3.循环合并
for(int i=0; i<len; i++){{
x1 +=a[i];
}
for(int i=0; i<len; i++){{
x2 *=b[i];
}
合并后:
for(int i=0; i<len; i++){{
x1 +=a[i];
x2 *=b[i];
}
适合于小循环
4.循环拆分
相对于循环合并,就是循环拆分。
-
语句级别
1.减少内存读写
2.选用尽量小的数据类型
3.结构体对齐
不同硬件平台和编译器对结构体对齐的要求不相同,
- 结构体占用总字节数尽量是2的幂
- 每个域的开始地址是他大小的整数倍,比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的
- 编译器提供了字节对齐的编译语句
GCC下则在每个结构加attribute((aligned(4))): - 对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐
1.数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
2.联合 :按其包含的长度最大的数据类型对齐。
3.结构体: 结构体中每个数据类型都要对齐。
struct stu{
char sex;
int length;
char name[10];
};
struct stu my_stu;
由于在x86下,GCC默认按4字节对齐,它会在sex后面跟name后面分别填充三个和两个字节使length和整个结构体对齐。于是我们sizeof(my_stu)会得到长度为20,而不是15.
struct stu{
char sex;
int length;
char name[10];
}__attribute__ ((aligned (4)));
- 表达式移除
去掉重复的、共同的计算或访问 - 分支优化
1.尽量避免把判断放到循环里
2.拆分循环,减少分支
3.合并多个条件
4.查表法移除分支
5.分支顺序。如:if( a&&b)若啊计算量大应该放后面
6.优化交换性能
uchar tmp=a[ji];
a[ji]=a[[jj];
a[jj]=tmp;
可优化为:
uchar aji=a[ji];
uchar ajj=a[[jj];
a[ji]=ajj;
a[jj]=aji;
后一段代码虽然多一个临时变量,但是读写间没有关系,并行读高!
-
指令级别
1.减少数据依赖
2.注意处理器多发射能力
3.优化乘除法和模余
整数运算最多一个周期,而乘法要三个周期,除法十几个,模余需要几十或上百,移位运算只要一个周期。
将除法转换成乘法
4.选择更具体的库函数或算法
5.其他:如声明float时加f后缀,使用const,static