您的位置:首页 > 编程语言

编写高效代码 之 优化内存访问

2012-10-09 12:54 267 查看
从理论上看,每条运算指令的执行时间都很短,大多数指令一个Cycle就能完成,很多时候还能一个Cycle执行多条指令,可是实际上,执行指令只是处理器要做的很少一部分工作,处理器还要从存储器中取指令,从存储器中将数据导入到寄存器中,等算完后,再将结果存入到存储器中。
处理器运算的速度像兔子赛跑一样快,但是存储器的访问速度像乌龟走路一样慢,而且越是远离内核的存储器,访问速度越慢。

下面这个表是在几个x86处理器中,内核访问各级数据Cache和内存所需要的 Cycle数:

处理器
Level 1 data Cache
Level 2 data Cache
内存
P3
3
8
140
P4
2
19
350
PM(奔腾M)
3
10
80
Core2(酷睿)
3
14
185
Opteron(皓龙)
3
13
100
从这张表可以看出,从L1中访问数据速度还较快,但是仍然要慢于运算的速度,从L2中访问数据速度还能将就,从内存中访问数据就无法忍受了。我们应该尽量减少内存的访问,要访问,也要尽量避免Cache miss。

少使用数组,少使用指针

由于大块数据会被放在存储器中,简单局部变量才会被放在寄存器中,因此应该尽量少用数组、指针,多用简单局部变量。

下面这段程序,需要4次内存访问:

c = a[i] * b[i];
d = a[i] + b[i];


如果改成如下的形式,就只有两次内存访问了:

x = a[i];
y = b[i];
c = x * y;
d = x + y;


少用全局变量

全局变量因为要被多个模块使用,不会被放到寄存器中,局部变量才能被放在寄存器中,应尽量避免使用全局变量。下面这段程序:

int  x;
int fun_a ()
{
int y, z;
y = x;
z = x + 1;
…
}


最好改为:

int  x;
int fun_a ()
{
int y, z, temp;
temp = x;
y = temp;
z = temp + 1;
…
}


一次多访问一些数据

我们通常会有这样的生活常识,要去很远的地方,就会多带一些东西,要去近一点的地方,就会少带一些东西。既然数据访问速度较慢,我们就一次多访问些数据。处理器将这些为我们考虑到了,通常都提供了较大的数据带宽。

以C64 DSP为例,通常一条指令,一次对两个32bit的数据做处理,而它却1次可以访问两个64bit数据。

在DSP中,SIMD指令和普通指令共用寄存器,有些数据虽然不能用SIMD指令处理,我们也可以一次将内存中的多个数据搬入到寄存器中,用简单指令分别处理,当要存储时,将分散的寄存器组合在一个连续的位置,再将数据输出到内存中。由于操作寄存器远比操作存储器快,虽然多了些数据的拆分和组合的操作,代价还是值得的。

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];
}
}


应该采用如下的写法,Cache的效率才高:

for(i=0; i<500; i++)
{
for(j=0; j<500; j++)
{
sum += a[i][j];
}
}


再来看另一个例子,在下面的这段代码中:

int a[4], b[4], i;
for (i = 0; i < 4; i++)
{
b[i] = Func(a[i]);
}


如果a和b数组存放在不同的Cache line中,一开始访问a会产生一次Cache miss,一开始访问b也会产生一次Cache miss,如果a和b数组存放在一个Cache line之中,则只会产生一次Cache miss。

在一起使用的数据放在一起能减少数据的Cache miss,在一起使用的函数放在一起能减少程序的Cache miss。

程序的组织也要符合Cache局部性原则。例如,一个程序大小为40K Bytes,经常使用的代码占据30K,很少使用的代码(如初始化、异常处理等)占据10K,指令Cache为32K Bytes,这段程序是无法完全放在Cache中的,我们可以将经常执行的代码放在一起,将很少使用的代码放在一起。这样经常使用的代码就能完全进入Cache中,减少了Cache miss。

有些较好的编译器能分析函数的调用关系,并合理的安排函数的存储位置,以提高指令Cache的命中效率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: