第六章 存储器层次结构
在简单模型中,存储器系统
是一个线性的字节数组,CPU能够在一个常数访问每个存储器位置。
- 虽然是一个行之有效的模型,但没有反应现代系统实际工作方式。
实际上,存储器系统(memory system)
是一个具有不同容量,成本和访问时间的存储设备的层次结构。
- CPU
寄存器
保存着最常用的数据。(0周期) - 靠近CPU的小的,快速的
高速缓存存储器(cache memory)
作为一部分存储在相对慢速的主储存器(main memory,简称主存)
中的数据和指令的缓冲区。(1~30周期) 主存
暂时存放 储存在容量较大的,慢速磁盘
上的数据。(50~200周期)磁盘
又作为存储在通过网络连接的其他机器的磁盘
或磁带
上数据的缓冲区。(几千万个周期)
6.1 存储技术
6.1.1 随机访问存储器
随机访问存储器(Random-Access Memory,RAM)
分为两类,静态的和动态的。
- 静态RAM(
SRAM
)比动态RAM(DRAM
)快地多,也贵得多。 SRAM
用来作为高速缓存存储器。(一般只有几兆)DRAM
用来作为主存以及图形系统的帧缓冲区(显存)。(一般有几G)
具体分析
-
SRAM
SRAM
将每个位存储在一个双稳态(bistable)存储器单元里。-
每个存储单元用一个六晶体管电路来实现。
- 有这样属性,可以无限期保持在两个不同的电压配置(configuration)或状态之一。
- 其他任何状态都是不稳定的。如图所示
-
由于这种双稳态特性,只要有电,它就会永远保持他的值,即使有干扰。
- 例如电子噪音,来扰乱电压,当消除干扰时,电路就会恢复稳定值。
-
-
动态RAM
-
DRAM
将每个位存储为对一个电容充电,这个电容非常小,通常只有30*10^-15法拉。- 因此,
DRAM
存储器可以造的十分密集。- 每个单元由一个电容和一个访问晶体管组成。
- 但是,
DRAM
存储器对干扰非常敏感。当电容电压被扰乱后,就永远不会恢复。
- 因此,
-
很多原因会漏电,使得
DRAM
单元在10~100毫秒时间内失去电荷。- 幸运的是,计算机的时钟周期以纳秒衡量,这个保持时间也相当长。
-
存储器系统必须周期性地读出,然后重写来刷新存储器的每一位。
- 有些系统也使用纠错码。
-
-
两者的对比
- 只要有供电,
SRAM
就会保持不变。 SRAM
不需要刷新SRAM
读取比DRAM
快SRAM
对干扰不敏感。- 代价是
SRAM
单元比DRAM
单元使用更多的晶体管,因而密集度低,更贵,功耗更大。
- 只要有供电,
-
传统的DRAM
-
DRAM芯片的单元被分为
d
个超单元(supell cell)
- 每一个个
超单元
由w
个DRAM单元组成。一个d*w
的DRAM
总共存储了dw
位信息。 - 超单元被组织成一个
r
行c
列的长方形阵列。- 这里
rc=d
,每个超单元有形如(i,j)
的地址,这里i
表示行,而j
表示列。
- 这里
- 注释: 计算机构架师称为
单元(cell)
,电路设计者称为字(word)
,避免混淆,取为超单元。
- 每一个个
-每个
DRAM
芯片被链接到某个称为存储控制器
的电路,这个电路可以一次传送w
位到每个DRAM
芯片或一次从每个DRAM
芯片传出w
位。-
为何有16个单元,却只要2位
addr
?- 首先发送一个行地址
i
给DRAM
。这叫RAS(Row Acess Strobe,行访问选通脉冲)。
DRAM
的响应是把第i行
全部拷贝到一个内部行缓冲区
。- 然后发送列地址
j
给DRAM
。这叫CAS(Column Access Strobe,列访问选通脉冲)
请求。 DRAM
的响应将内部行缓冲区
的超单元(i,j)发送给存储控制单元。
- 首先发送一个行地址
-
为何设计成二维阵列,而不是线性?
- 一个原因是降低芯片上地址引脚的数量。原本需要2N 的引脚,只要N个就好了。
- 缺点是需要传输两次。增加访问时间。
- 一个原因是降低芯片上地址引脚的数量。原本需要2N 的引脚,只要N个就好了。
-
-
存储器模块
DRAM芯片包装在
存储器模块(memory module)
中,它是插到主板的扩展槽上。- 常见的包装包括
168
个引脚的双列直插存储器模块(Dual Inline Memory Module)
,它以64
位为块传送数据到存储控制器和从存储控制器传出数据。 - 还包括
72
个引脚的单列直插存储器模块(Single Inline Memory Module)
,它以32
位为块传送数据。
如图展示一个存储器模块的基本思想。
- 用对应超单元地址
(i,j)
的8
个超单元
来表示主存字节地址A处的64
位双字(此处:字=4字节)。 - 通过将多个
存储器模块
连接到存储控制器
,能够聚合主存。在这种情况下,当控制器收到一个地址A时,控制器选择包含A的模块k
,将A转换成它的(i,j)
形式,并发送(i,j)
到模块k
(不太懂这句话什么意思?)
- 常见的包装包括
-
增强的DRAM
基于传统DRAM,进行优化,改进访问速度。
-
快页模式DRAM(Fast Page Mode DRAM,FPM DRAM)。
- 传统的
DRAM
将超单元的一整行拷贝到它的内部缓冲区,使用一个,然后丢弃剩余的。 FPM DRAM
允许对同一行连续地访问可以直接从行缓冲区获得服务。
- 传统的
-
扩展输出DRAM(Extended Data Out DRAM,EDO DRAM)
不懂
FPM DRAM
的一个增强形式,它允许单独的CAS
信号在时间上靠的紧密一些。
-
同步DRAM(Synchronous DRAM,SDRAM)
不懂
- 就它们与存储控制器通信使用一组显示的控制信号来说,常规的,
FPM
,EDO
都是异步的。 - 最终效果就是
SDRAM
能够比那些异步的存储器更快输出超单元内容。
- 就它们与存储控制器通信使用一组显示的控制信号来说,常规的,
-
双倍数据速率同步DRAM(Double Date-rate Synchronous DRAM,
DDR SDRAM
)不懂
DDR SDRAM
是对SDRAM
的一种增强。- 通过使用两个时钟沿作为控制信号,从而使
DRAM
速度翻倍。 - 不同类型的
DDR SDRAM
是用提高有效带宽的很小的预取缓冲区的大小来划分的:DDR(2位)
DDR2(4位)
DDR3(8位)
-
Rambus DRAM(RDRAM).
不懂
- 这是一种私有技术,它的最大带宽比
DDR SDRAMD
的更高。
- 这是一种私有技术,它的最大带宽比
-
视频 RAM(Video RAM,VRAM).
不懂
- 它用在图形系统的帧缓冲区中,
VRAM
的思想与FPM
类似,两个主要区别是VRAM
的输出通过依次对内部缓冲区的整个内容进行移位得到。VRAM
允许对存储器并行地读或写。
- 它用在图形系统的帧缓冲区中,
-
-
非易失性存储器(ROM)。
非易失性存储器(nonvolatie memory)
即使在关电后,也仍然保存他们的信息。DRAM
和SRAM
是易失的(volatile).
- 由于历史原因,虽然
ROM
有的类型既可以读也可以写,但是它们整体被称为只读存储器(Read-Only Memory,ROM)
。
ROM
通过以它们能够被写的次数和对他们进行重编程的方式所用的机制区分。-
PROM (Programmable ROM,可编程ROM)
只能够被编程一次。PROM
的每个存储器有一个熔丝,它只能用高电流熔断一次。 -
可擦写可编程ROM(Erasable Programmable ROM,EPROM)
有一个透明的石英窗口,允许光到达存储单元。- 紫外线光照射过窗口,
EPROM
单元就被清除为0. - 对
EPROM
编程通过使用一种把1
写入EPROM
的特殊设备完成。 EPROM
可擦写次数达到1000次
- 紫外线光照射过窗口,
-
电子可擦除PROM(EEPROM)
类似于EPROM
,- 但是它不需要一个物理上独立的编程设备,直接在印刷电路卡上编程。
EEPROM
能够被编程次数的数量级可以达到10^5
次。
- 但是它不需要一个物理上独立的编程设备,直接在印刷电路卡上编程。
闪存(flash memory)
也是一类非易失性存储器,基于EEPROM
,已经成为一种重要的存储设备。- 在6.1.3节,我们会仔细研究一种新型的基于闪存的磁盘驱动器,称为
固态硬盘(Solid State Disk,SSD)
.- 它能够提供相对于传统旋转磁盘更快速,更强健和更低耗能的选择。
存储在
ROM
设备中的程序通常称为固件(firmware)
。- 一些系统在固件中提供了少量基本的输入和输出函数。
- 例如,PC的
BIOS(基本输入/输出系统)
的例程。
- 例如,PC的
-
访问主存
数据流通过称为
总线(bus)
的共享电子电路在处理器和DRAM
主存之间来来回回。- 总线事务(bus transaction):每次CPU和主存之间的数据传送都是通过一系列步骤来完成的,这些步骤称为总线事务(bus transaction).
- 读事务(read transaction): 主存把数据给CPU。
- 写事务(write transaction): CPU把数据写入主存。
总线
是一组并行的导线,能携带地址,数据和控制信号。- 取决于总线的设计,数据和地址信号可以使用一条,也可以使用不同的。
- 同时,两个以上的设备也能共享一根总线。
- 控制线携带的信号为同步事务,标示当前执行的事务类型。
示例计算机系统的配置,
CPU
芯片,I/O
桥的芯片组,组成DRAM
的存储器模块。- 有一对
总线
- 系统总线: 链接
CPU
和I/O
桥。 - 存储器总线:链接
I/O
桥和主存。
- 系统总线: 链接
I/O
桥- 将系统总线的电子信号翻译成存储器总线的电子信号。
I/O
桥也将系统总线
,存储器总线
连接到I/O总线
。- 磁盘和图形卡这样
I/O
设备共享I/O
总线。
- 磁盘和图形卡这样
关于总线设计的注释
总线设计是计算机系统中的一个复杂而且变化迅速的方面。不同的厂商提出了不同的总线体系结构,作为产品差异化的一种方式。- Intel 系统使用
北桥(northbridge)
和南桥(southbridge)
的芯片组分别将CPU连接到存储器和I/O设备。 - 在比较老的系统,Pentium和Core 2 系统中,
前端总线(Front Side Bus,FSB)
将CPU连接到北桥
。 - AMD 将
FSB
替换为超传输(HyperTransprot)互联
. - Core i7 使用的是
快速通道(QuickPath)
互联。
接下来我们会集中于
存储器总线
上。movl A,%eax
- 这里地址A的内容被加载到寄存器%eax中,有以下步骤。
- CPU芯片上称为
总线接口
的电路发起总线上的读事务
。 - CPU将地址A放到系统总线上。
I/O桥
将信号传递到存储器总线
。- 主存感觉到
存储器总线
上的地址信号,从存储器总线读地址,从DRAM取出数据字,并将数据写到存储器总线。 I/O桥
将信号翻译称系统总线
信号,然后沿着系统总线
传递。- 最后,CPU感受到
系统总线
上的数据,从总线上读数据,拷贝到寄存器中。
- CPU芯片上称为
如果是
写事务
movl %eax,A
- 这里寄存器%eax的内容写入地址中中,有以下步骤。
- CPU将地址放到
系统总线
。存储器从存储器总线
读出地址,并等待数据到达。 - CPU将数据放到
系统总线
。主存从存储器总线
读出数据,并拷贝到DRAM中。 - 以上两个步骤是并行的。(如果数据总线和地址总线分开的话)
- CPU将地址放到
- 总线事务(bus transaction):每次CPU和主存之间的数据传送都是通过一系列步骤来完成的,这些步骤称为总线事务(bus transaction).
6.1.2 磁盘存储
磁盘
是广为应用的保存大量数据的存储设备,存储数据的数量级可以达到1TB,1PB等等,而基于RAM
的 从几百到几千M字节。不过,从DRAM
中读比磁盘快10万倍,从SRAM
读比磁盘快100万倍。
6.1.2.1 磁盘构造
磁盘
是由盘片(platter)
构成的.
-
每个
盘片
有两面或者称为表面(surface)
。 -
盘片
中央有一个可以旋转的主轴(spindle)
,它使得盘片以固定的旋转速率(rotational rate)
旋转,通常是5400~15 000转每分钟(Revolution Per Minute,RPM)
- 磁盘通常含一个或多个这样的盘片,并封装到一个密封的容器里。
如图,展示了一个典型的磁盘表面的结构。
- 每个
表面
是由一组称为磁道(track)
的同心圆组成的。- 每个
磁道
被划分为一组扇区(sector)
。- 每个
扇区
包含相等数量的数据位(通常是512
字节),这些数据编码在扇区的磁性材料中。 扇区
之间由一些间隙(gap)
分隔- 不存储数据
间隙
存储用来标识扇区的格式化位。
- 每个
- 每个
- 磁盘是由一个或多个叠放在一起的
盘片
组成,被封装在密封包装。-
整个装置称为
磁盘驱动器(disk drive)
,我们通常简称为磁盘(disk)
. -
有时又叫
旋转磁盘(rotating disk)
,使之区别基于闪存的固态硬盘(SSD)。SSD
没有可移动的地方
-
磁盘商通常用术语
柱面(cylinder)
描述多个盘片
。的构造 。柱面
是所有盘片表面上到主轴中心的距离相等的磁道集合。- 例如,一个驱动器有三个盘片,六个面。那么
柱面k
是六个磁道k
的集合。
-
6.1.2.2 磁盘容量
-
远古时期,磁道上的扇区是固定的,所以扇区的数目由靠内磁道能记录的扇区数决定。
-
现在通过
多区记录
技术。- 在这种技术中,柱面的集合被分割为不可相交的子集合,称为
记录区
。- 每个
区
包含一组连续的柱面。 - 一个区的每个柱面都有相同的扇区数。
- 扇区数也是由一个区最里面磁道的扇区数决定。
- 每个
- 在这种技术中,柱面的集合被分割为不可相交的子集合,称为
-
下面给出一个容量计算公式。
G
,M
,K
大小依赖于上下文,磁盘
和RAM
中所对应的大小一般不同。
6.1.2.3 磁盘操作
//磁盘想暂时跳过。之后看。
6.1.3 固态硬盘 暂时跳过
6.1.4 存储技术趋势 暂时跳过
6.2 局部性
-
局部性(locality)
:倾向于引用临近于其他最近引用过的数据项的数据项。或者最近引用过的数据项本身。- 这种倾向被称为
局部性原理
,是一个持久的概念,对硬件,软件设计都有极大影响。
- 这种倾向被称为
-
局部性
通常有两种不同的形式:时间局部性(temporal locality)
.- 被引用过一次的存储器很可能在不远的将来再被多次引用。
空间局部性(spatial locality)
.- 一个存储位置被引用了一次,在不远的将来,很可能引用附近的位置。
-
现代计算机系统各个层次都利用了
局部性
。-
硬件层,局部性原理允许计算机设计者引入
高速缓存存储器
。- 保存最近被引用的指令和数据项,从而提高对主存的访问速度。
-
操作系统级
- 局部性原理允许使用
主存
作为虚拟地址空间
最近被引用块的高速缓存。 - 利用主存缓存磁盘文件系统最近使用的磁盘块。
- 局部性原理允许使用
-
应用程序设计
- Web浏览器将最近被引用的文档放到本地磁盘上,利用的就是时间局部性。
-
6.2.1 对程序数据引用的局部性
-
考虑这个
简单函数
的局部性。-
sum
具有良好的时间局部性
- 在一段时间内被多次访问。
-
v数组
具有良好的空间局部性
。- 一段时间内临近的元素被多次访问
-
所以该程序具有良好的
局部性
。
-
-
像
sumvec
这样顺序访问一个向量中每个元素的函数,称为具有步长为1的引用模式
(stride-1 reference pattern)。- 有时称
步长为1的引用模式
叫做顺序引用模式
。 - 每隔
k
个访问,叫做步长为k
的引用模式。 步长为1的引用模式
是空间局部性常见和最重要的来源。- 步长越大,空间局部性越低。
- 有时称
-
对于二维数组,两种不同的循环方式,空间局部性的差异十分大。
- 行优先顺序,是最好的。
6.2.2 取指令的局部性
因为程序指令被放在存储器中,CPU需要读出这些指令,所以也能取指令的局部性。
-
顺序执行
:良好的空间局部性。 -
for
: 良好的时间局部性。 -
代码区别于数据的地方,执行后,不会被修改。
6.2.3 小结
在我们学习了高速缓存存储器以及它们是如何工作的之后,我们会介绍如何用高速缓存命中率和不命中率来量化局部性的概念。
6.3 存储器层次结构
6.3.1 存储层次结构中的缓存
一般而言,高速缓存(cache,读作"cash")
是一个小而快速的存储设备,它作为存储在更大,也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存(caching,读作"cashing")
。
- 存储器层次结构的中心思想是:层次结构中的每一层都是来自较低一层的
缓存
。
-
第
k+1
层被分为连续的数据对象片(chunk)
,称为块(block)
。- 数据总是以
块
作为传送单元,在存储层间。 - 传输一个字节,和传输一个块的时间上差不多
- 块使空间局部性得到发挥
- 数据总是以
-
缓存命中
-
缓存不命中
- 替换/驱逐 块
- 牺牲块
替换策略
- 随机替换策略
- 最近最少被使用替换策略(LRU)
-
缓存不命中的种类
-
一个空的缓存称为
冷缓存
- 此时的不命中叫做
冷不命中
,强制性不命中
。 - 短暂的事件。
- 在缓存热身后的稳定状态不会出现。
- 此时的不命中叫做
-
冲突不命中
- 限制性的放置策略中可能出现
-
容量不命中
- 工作集超过缓存大小。
-
-
缓存管理
- 管理缓存可以是硬件,也可以是软件,可以是两者的结合。
寄存器文件
:编译器管理.L1,L2,L3
:内置在缓存中的硬件逻辑管理。DRAM,内存
由地址翻译硬件和操作系统共同管理。本地磁盘
: 由软件管理。
- 管理缓存可以是硬件,也可以是软件,可以是两者的结合。
6.3.2 存储器层次结构概念小结
基于缓存的结构行之有效。
- 较慢的存储器比块的存储器便宜。
- 能很好利用局部性。
- 利用
时间局部性
:由于时间局部性,同一数据对象可能被多次使用,一旦一个数据对象在第一次不命中时被拷贝到缓存中,我们就会期望后面对该目标有一些的缓存命中。 - 利用
空间局部性
:块通常包含多个数据对象,由于空间局部性,我们希望对该块中其他对象节约的访问时间补偿不命中时的拷贝时间。
- 利用
6.4 高速缓存存储器
6.4.1 通用的高速缓存存储器结构。
S
个高速缓存组。- 每个组有
E
个高速缓存行 - 每行是由一个
B=2^b
字节的数据块
组成的,一个有效位
,还有t=m-(b+s)个标记位
。 m
:地址长度
高速缓存的结构可以用元组(S,E,B,m)
描述。
- 高速缓存的大小
C
指所有块大小的和。标记位和有效位不包括在内。C=S*E*B
;
高速缓存如何寻址(以地址A)?
S
->标记位
->块偏移
- 对应组选择,行匹配,字抽取
6.4.2 直接映射高速缓存
根据E
高速缓存分为不同的类
E=1
时叫直接映射高速缓存
E>1
时叫组相连高速缓存
S=1
时叫全相连高速缓存
抽取请求字的过程,分为三步:
- 组选择
- 行匹配
- 字抽取
不命中时
- 行替换
过于简单就不详细描述了。
直接映射高速缓存中的冲突不命中
-
如果
x[i]
与y[i]
恰好在同一个高速缓存组。- 会不停的
冲突不命中
。 - 在
x
和y
块之间不停的抖动
。抖动
描述的就是,高速缓存反复加载和驱逐相同的高速缓存块。
- 会不停的
-
避免方法
- 改变
x
数组大小。 组相连高速缓存
- 改变
为什么用中间的位来做索引。
如果用高位作索引,那么组内的数据在物理地址也是相邻的。不能充分利用
空间的局部性
。
6.4.3 组相连高速缓存
一个1<E<C/B
的高速缓存通常称为E路组相连高速缓存
。
组相连
高速缓存中的组选择
与之前的一样,通过组索引位
。
组相连
高速缓存中的 行匹配 和 字选`。
有效位
和标记位
组相连
高速缓存中不命中时的行替换
随机
最不常用(Least-Frequently-Used,LFU)
- 频数最低
最近最少使用(Least-Recently-Used,LRU)
- 时间最久远
6.4.4 全相连高速缓存
一个全相联高速换粗(full associative cache)
是由一个包含所有高速缓存行的组(即E=C/B
)组成的。
全相连
的组选择
- 只有一个组没啥好说的
全相连
的 行匹配 和 子选择
- 和组相连一样,但是规模更大。
- 所以只适用于做小的高速缓存
- 如
TLB
,缓存页表项
- 如
- 所以只适用于做小的高速缓存
6.4.5 有关写的问题
处理写命中
:
-
直写
:立即将w
的高速缓存块写会到紧接着的下一层。- 优点:简单
- 缺点:引起总线流量,速度慢
-
写回
:尽可能地推迟操作,只有当替换算法要驱逐时更新。- 优点:减少总线流量。
- 缺电:复杂,额外维护一个
修改位
。
处理写不命中
-
写分配
:加载相应的低一层块到高速缓存中,然后更新这个高速缓存块。- 跟
写回
搭配。
- 跟
-
写不分配
:直接写到低层。- 跟
直写
搭配。
- 跟
6.4.6 一个真实的高速缓存层次结构的解剖
- 高速缓存,指令,数据
- 只保存
指令
的高速缓存叫做i-cache
- 只保存
程序数据
的高速缓存叫做d-cache
- 两者都保存的高速缓存叫做
unified cache
- 只保存
6.4.7 高速缓存参数的性能影响
不命中率(miss rate)
:不命中数量/引用数量。命中率(hit rate)
: 1-不命中率 。命中时间(hit time)
: 从高速缓存传送一个字到CPU的时间。不命中处罚(miss penalty)
:由于不命中的额外时间。- L1:10
- L2:40
- L3:100
优化高速缓存的成本和性能的折中是一项很精细的工作,这里只能简单讨论。
-
高速缓存大小的影响
- 提高命中率
- 增加命中时间
-
块大小的影响
- 更好利用
空间局部性
,提高命中率 - 影响
时间局部性
不命中处罚
越长- 现代系统一般在32~64字节中。
- 更好利用
-
相连度影响
- 降低了高速缓存中的抖动的可能性
- 好的替换策略能大大增强效率(提高命中率)。
- 价格更昂贵,命中时间更长,不命中处罚更多。
- 所以是处罚时间(
(1-命中率)*不命中处罚
)和命中时间的折中。
- 不命中处罚越高用这个。让处罚时间更少
- 命中率提高
- 不命中处罚增长忽略不计。
-
写策略影响
- 层次越往下,越用
写回
。
- 层次越往下,越用
6.5 编写高速缓存友好代码
与之前6.2.1 类似的介绍
-
对局部变量反复利用
-
步长为1的循环
-
二维数组,先行遍历
6.6 综合:高速缓存对程序性能的影响
6.6.1 存储器山
一个程序从存储系统中读数据的速率称为读吞吐量(read throughput)
,或者有时称为读带宽(rand bandwidth)
。
- 通过
读吞吐量
来分析存储器性能。
存储器山是一种综合研究存储器层次结构的工具。它反映了存储器层次结构中不同层次的带宽。也反映了具有不同的时间局部性与空间局部性的程序的性能。通过分析存储器山的数据,还可以看出存储器系统的部分硬件参数。
6.6.2 重新排列循环以提高空间局部性
考虑n*n
的矩阵乘法问题。
C[i][j]+=A[i][k]*B[k][j]
i
,j
,k
三层循环的顺序无所谓,都能得到正确的结果。- 但是时间差异却十分大。
代码如下:
显然能看出kij
版本和ikj
版本都十分优秀
- tisp:
r=A[i][k]
在i,k
循环或k,i
循环下,其实差不了多少,因为n次循环才调用一次。 - 也因此上图左右相邻的两个版本的复杂度都会差不多。
网络旁注;使用
分块
来提高空间局部性
就是类似将数据结构尽量设计成块。能提高效率,但会使得代码更难懂。它更适合优化编译器或者频繁执行的库函数。
6.6.3 在程序中利用局部性
- 精力注重内循环
- 步长为1,
- 一旦读入一个对象,就尽可能多使用它。
6.7 小结
-
基本存储技术包括
RAM
,ROM
和磁盘。RAM
有两个类型SRAM
DRAM
SDRAM
DDR
-
程序通过编写良好的局部性代码利用好缓存。