性能优化-编码优化(C语言)不涉及设计上的性能优化,针对语言coding层级的,函数编写级别的技巧;(里面部分优化技能没有经过验证)
整体思路与误区
当前编译器的优化其实已经做了很多工作,很多时候我们想当然的任务更优的代码,实际上在编译器的优化下,它的汇编指令基本一致的。
个人的理解,在编码过程中以可读性优先,没有必要为了性能牺牲可读性,适当权衡性能编码方式(很多性能优化的写法一定程度上都影响了可读性、可维护性)。如果是热点代码,频繁调用的代码 可以侧重一些,如果本身代码执行就不是性能主要路径,那么我建议还是可读性优先。 如果希望做性能优化,还是建议在热点路径上有测试数据支持的情况下针对性优化。
总而言之,在保持良好可读性条件下添加一些高性能编码习惯,个人认为才是合理方式。编译器优化功能对那些平铺直叙的代码更有效,避免在编码里面加入一些想当然的”花招“,这反而会影响编译器优化。
(性能优化优先级:系统设计》数据结构/算法 选择》热点代码编码调整)
函数调用优化
减少函数的参数,无需返回值就明确定义void
函数入参低于一定数量(如4个),则形参由R0,R1,R2,R3四个寄存器进行传递;若形参个数大于的部分必须通过堆栈进行传递,性能降低;用指针传递的效率高于结构赋值;直接用全局变量省去了传递时间,但是影响了模块化和可重入,要慎重使用;
如果函数不需要返回值就明确void;
异常分支独立函数
避免小函数调用开销(提炼宏函数 或 inline内联化)
int afunc(char *buf, bool enable) {
if (check_null(buf) == true) {
return -1;
}
}
// 优化为:
#define check_null(a) if (a==null) { return -1;}
int afunc(char *buf, bool enable) {
check_null(buf); // 宏函数,降低调用开销
}
分支预测优化
安排判断的顺序,让运行最快和最可能发生的分支首先被执行。
likely/unlikely
if语句的预测
switch-case分支预测
条件判断次序
变量与内存访问优化
减少不必要的赋值/变量初始化 和 不必要临时变量
减少不必要的赋值或者变量初始化,减少不必要的临时变量;
int i = 0;
i = input_value;
// 优化:
int i = input_value
int ret = do_next_level_fuc();
return ret;
// 优化:
return do_next_level_fuc();
尽可能使用const常量
const常量如果声明的对象地址不被获取,允许编译器不对他分配存储空间,可以生成更高效的代码;
减少全局变量的访问
全局变量的使用会妨碍编译器的优化行为,不要到处都是全局变量直接访问,应该收敛到一个固定的入口层次,内部的函数都应该对全局变量不直接可见。
register变量使用
register变量可以建议编译器把该变量放置在寄存器而不是堆栈上,CPU可以更快的读写访问;
结构体布局对齐/本地变量排布布局
很多编译器都有 数据结构 双字或者四字对齐配置,分配给结构体成员空间顺序也可能和声明的不一致。但是部分编译器不提供这些功能或者效果不好。一般情况对齐原则struct大小规则(默认情况,未指定对齐编译命令下):1、每个成员偏移量为其成员size整数倍,若不是在其前插入字节填充;2、所有成员计算完成后,总大小必须是最大成员整数倍,若不是在尾部添加字节填充;
建议:1、把长类型放在短类型前面,避免内存空洞;2、补齐结构体为最长成员整数倍;(本地变量排序类似)
struct{
char a[5];//存在空洞
long b;
double c;
} type_a;
// 优化为:
struct{
double c;
long b;
char a[5];
char reserve[7];
} type_b;
复杂结构体变量间直接赋值比memcpy效率高
循环体优化
循环展开,降低循环层次或者次数
while (i < count) {
a[i] = i;
i++
}
// 优化为:
while (i < count - 1) {
a[i] = i;
a[i+1]=i+1;
i+=2;
}
if (i == count - 1) {
a[count - 1] = count - 1;
}
循环合并(计数器相同的),避免多次轮询
if (i = 0; i < index; i++) {
do_type_a_work(i);
}
if (i = 0; i < index; i++) {
do_type_b_work(i);
}
// 优化为:
if (i = 0; i < index; i++) {
do_type_a_work(i);
do_type_b_work(i);
}
循环内计算外提(每次计算不变),降低无效计算
for (int i = 0; i < get_max_index(); i++) {}
// 优化为:
int max_index = get_max_index();
for (int i = 0; i < max_index; i++) {}
循环内多级寻址外提,避免反复寻址跳转
for (int i = 0; i < max_index; i++) {
ainfo->bconfig.cset[i].index = index;
ainfo->bconfig.cset[i].flag = flag;
}
// 优化:
set = ainfo->bconfig.cset;
for (int i = 0; i < max_index; i++) {
set[i].index = index;
set[i].flag = flag;
}
循环内判断外提(某时刻结果不变),降低无效比较次数
if (i = 0; i < index; i++) {
if (type==TYPE_A) {
do_type_a_work(i);
} else {
do_type_b_work(i);
}
}
// 优化:
if (type==TYPE_A) { // 提高性能的同时,影响了可维护性;
if (i = 0; i < index; i++) {
do_type_a_work(i);
}
} else {
if (i = 0; i < index; i++) {
do_type_b_work(i);
}
}
循环体使用int类型
多重循环 最忙的循环放最里面
for (column = 0; column < 100; column ++) {
for (row = 0; row < 5; row++) {
sum += table[row][column ];
}
}
// 优化
for (row = 0; row < 5; row++) {
for (column = 0; column < 100; column ++) {
sum += table[row][column ];
}
}
方向不敏感的循环采用递减取代递增
大部分MCU都有为0转移的指令,把index和0比较,编译器更容易优化,执行速度也更快;
for (int i = 0; i < 1000; i++) {}
// 优化为:
for (int i = 1000; i > 0; i--) {}
数学计算优化
使用加减位移替代乘除(个人不建议)
a /= 2; b *= 2; c *= 5;
// 优化
a >> 1; b << 1; c = c << 2 + c;
(个人不建议,非热点代码使用这些技巧强烈反对:1、影响可读性 2、很多时候编译器本身优化就做得很好(经常优化后汇编代码完全一致的))
用位与替代取余操作
if (a%2 == 1) { };// 对齐判断
num += 4 - num % 4; //对齐操作
// 优化
if ((a & 1) ==1) {};
num = (num + 3) & 0xfffffffc;
使用自增自减替代+1,-1
x=x+1;
//优化为:
x++;
性能更高,大部分cpu指令无需读取,增加,写回,而是直接增量操作符执行;
提取计算过程公共表达式,避免重复计算,特别是除法
用低级别语言重写代码
简化复杂逻辑(其他)
用查表法替代复杂逻辑
缓存操作:减少实时性不高的函数调用及消息
参考资料
《代码大全》