从理论上看,每条运算指令的执行时间都很短,大多数指令一个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指令处理,我们也可以一次将内存中的多个数据搬入到寄存器中,用简单指令分别处理,当要存储时,将分散的寄存器组合在一个连续的位置,再将数据输出到内存中。由于操作寄存器远比操作存储器快,虽然多了些数据的拆分和组合的操作,代价还是值得的。