目前CPU运行速度远超过内存访问速度,且从趋势看这种速度差距还会越拉越大,提高内存访问效率将是软件优化重要而长期的课题。内存访问优化的一般性措施可大体分两方面:1)减少内存访问;2)调整代码使程序集中顺序地访问内存。
一、减少内存访问的措施包括:
a.充分利用寄存器
充分利用寄存器缓存数据,是减少内存访问的思路之一。C程序编译后哪些元素由寄存器存储,哪些又会放进内存,取决于CPU以及对应的编译器规范。以arm为例,对于遵循ATPCS规则的编译器:
1)函数前4个参数放在寄存器里,超出4个则压入栈内存。
2)局部变量如果没有取址操作,或有取址但未赋给其他变量,就会被编译器优先安排寄存器存储,如寄存器已占完,则开始在寄存器和栈之间交换存储。
不同CPU及编译器有类似规范,注意下面几点能更充分地利用寄存器:
1)如果函数参数过多,把多个参数组织成结构体,传递结构体指针。在很多平台上,这样能减少参数入栈的几率。
2)用register提示编译器把关键变量或循环内变量用寄存器缓存,register暗示编译器变量将被频繁使用,应将其保存在寄存器中,以加快其存储速度,但要注意它仅仅是个提示,很多时候编译器并不鸟它。
3)把某些大函数拆分成小函数,防止因寄存器不足导致局部变量在栈和寄存器之间反复存取,类似内存和硬盘间的内容交换,浪费时间。大函数内局部变量多,情况复杂,编译器无法分析清每个局部变量的作用范围,常常做出很多无用的压栈出栈操作。
4)如某热点函数内经常访问全局变量,可添加一个临时局部变量,诱导编译器将该全局变量内容读到寄存器中作为其影子,对寄存器进行相关操作,最后赋回全局变量,以减少内存访问。如:
long product;
void factorialA(long n)
{
long i;
for(i = 1; i <= n;i++ ){ product *= i; }
}
void factorialB(long n)
{
long i
long x = 1;
for(i = 1; i <= n;i++ ){ x *= i; }
product = x;
}
n较大时,上面两个函数性能有显著差别,这就是充分利用寄存器的好处。
5)避免局部变量取址, 编译器处理局部变量时一般先尽量用通用寄存器缓存,但如果有局部变量取址操作,意味着该变量只能放在栈内存(通用寄存器没有内存地址概念)。如果该局部变量在循环中多次读写,此时也同样可考虑增加中间变量,用完后再写回。比如下例改动就能提高整体效率:
void f(int *a);
int g(int a);
int test1(int i)
{
int j;
f(&i);
for(j =0;j<1000;j++)
i += g(i);
return i;
}
修改后
int test2(int i)
{
int temp = i;
f(&temp);
i = temp;
for(j =0;j<1000;j++)
i += g(i);
return i;
}
test2中使用了变量的拷贝temp,把temp的地址传入函数f(),函数f()退出时再把temp回赋给i,这样变量i不存在取址操作,编译器就能把它用寄存器保存。这里循环内对i有数千次访问,循环体中的i放在寄存器相比放在栈内存,效率差别相当大。