首页 > 代码库 > 代码优化

代码优化

代码优化

并非所有的处理器级优化策略仅限于汇编。
即使C这样的高级语言中,也由不少适用的规则。

减少上下文依赖

看如下程序:

double list[100];double sum = 0;for (int i = 0; i < 100; i++) {    sum += list[i];}
上面这段代码还可以优化吗?站在C语言层面来说没有,但是站在处理器层面有: 
double list[100];double sum1=0, sum2=0;for (int i = 0; i < 100; i+=2) {    sum1 += list[i];    sum2 += list[i+1];}double sum = sum1+sum2;
循环展开

建议:不推荐
优点:减少循环次数(条件跳转次数)
缺点:增加了代码量,占用更多CPU内部一级代码高速缓冲区,可读性变差
描述:由于现代CPU具有分支预测技术,条件跳转已经很快,所以循环展开效果有限,而且这里累加每次都依赖于上一次的加法运算结果,这是一个线性的处理过程,而现代处理器在微操做级别,可以将临近多条指令同时处理,循环展开后就可以将一个线性处理链拆分为两个,那么处理器就可以同时处理两次加法运算

低效的静态变量

静态变量是在用到的时候才分配,而静态变量是一开始就分配好的,那么静态变量有更高的效率?
错误!局部变量存在于堆栈上,对其空间的分配,仅仅是在一个变量声明的语句块中,仅依靠单次修改esp寄存器就可以实现(一组局部变量的声明只需一次)
把变量放在堆栈上,带来的最大好处:函数能重复使用内存,这块内存被反复读写时,其数据就好存在于CPU内部一级缓存中,访问速度非常快。
绝大多数情况下,堆栈顶部的数据就符合这个条件,而静态变量则不同,内存和CPU内部缓存的数据交换,往往成为程序的速度瓶颈。

如果需要一个临时对象,使用new操作间接的调用malloc是十分低效的,高效做法应该是改用alloca在堆栈中分配内存。

注意绝大多数情况,有人喜欢局部声明巨大的数组,这很容器造成栈溢出,又会使得CPU内部缓存的映射不停的变动,破坏了CPU高度缓存带来的好处。所以要尽量避免。

若你需要一组全局变量,那么把它们放到一个类中,通过成员函数访问也可以提高效率(单例),通过唯一的this指针访问对CPU缓存很有利。

数据的组织

如果几个数据具有相关性,处理其中一个后很快就会处理接下来的另外几个,那么让这些数据物理上挨在一起效率更优。 

CPU处理数据时,最希望数据以对齐的方式存在于内存中,4字节和小于4字节的变量类型,希望以4字节方式对齐(即内存地址为4的位的地方) 而double是8字节,以8字节对齐最高效,还有比较大得结构,则希望以16字节对齐。

除法运算

整数运算和浮点运算中,除法都是很慢的指令,所以我们应该极力避免或者减少除法运算 注意:现代编译器会做很多优化工作,比如n/8优化为移位操作,a/3.14转化为乘法。
计算3次乘法也比计算一次除法速度快

避免过大的循环

比如:

// 方式1:for (int i = 0; i < 100; i++) {    todo_1();    todo_2();}// 方式2:for (int i = 0; i < 100; i++)    todo_1();for (int i = 0; i < 100; i++)    todo_2();

上面程序哪个效率更好?
如果两个函数处理的逻辑都很简单,那么第一种方式更高效
如果两个函数处理的逻辑都很复杂,那么第二种方式更高效,原因在于CPU的代码缓存机制。
通常,程序代码第一次运行速度比较慢(如for循环第一次进入)。这存在一个代码从内存加载到CPU得时间,分支预测缓存的建立,甚至CPU对指令的解码。
如果循环体内涉及到的代码超过CPU的代码缓存容量,就会使得每次循环都会重新做一系列缓存工作,所以尽量维持循环中代码的尺寸。

参考:云风《游戏之旅-我的编程感悟》

代码优化