13.7 系统体系结构
内存访问的代价差别很大。在某个特定的RISC体系结构中,若数据位于数据缓存中,那么访问它需要耗费一个CPU周期;若位于主存中(缓存失败),则需要8个CPU周期;若位于硬盘上(页面错误),则需要400,000个CPU周期。虽然具体的数值会发生变化,但对于不同的处理器体系结构,周期数在总的关系上是一致的:缓存成功、缓存失败和页面错误之间的速度相差多个数量级。
当访问数据时,最先搜索的是数据缓存。若数据不在缓存中,硬件产生缓存失败信号,该信号会从RAM或硬盘加载数据至缓存中。缓存以缓存行为单位,通常加载比我们所寻找的特定数据项更大的一块数据。这样的话,在4字节整数上的缓存失败可能导致加载128字节的缓存行到缓存中。由于相关数据项的位置在内存中很可能相邻,因此这对我们很有用:如果随后的指令尝试访问相同缓存行中的其他数据项,那么我们第一次访问该数据时就将缓存成功,能较快地获取数据。我们应该尽可能帮助自己的代码显示这种引用的位置。
以类X为例:
- class X {
- public:
- X() : a(1), c(2) {}
- ...
- private:
- int a;
- char b[4096]; // 缓冲区
- int c;
- };
构造函数X::X()初始化成员a和c。符合标准的编译器将按声明顺序来排列对象X:成员a和成员c被4096字节的成员b分离,因而不会出现在相同的缓存行内。当X::X构造函数访问c时有可能遇到缓存失败。
既然a和c被相邻的指令访问,那么我们不妨将它们放在相邻的位置,使得缓存成功率更高。
- class X {
- ...
- private:
- int a;
- int c;
- char b[4096]; // 缓冲区
- };
现在a和c更有可能位于相同的缓存行。因为a在c之前被访问,所以当我们需要访问c的时候,基本可以保证c在数据缓存中。