今天我们聊聊CPU的指令缓存和数据缓存,即iCache和dCache,他俩就是离CPU最近的缓存了。
对Cache还不太熟悉的朋友可以先看看我的系列文章:
1高速缓存基本原理:
2高速缓存中的歧义和别名:
3高速缓存一致性:
4理解高速缓存对写代码的重要性:
5高速缓存的历史
6自旋锁的发展和高速缓存的关系:
CPU和主存之间也存在多级高速缓存,一般分为3级,分别是L1, L2和L3。
但是L1 Cache比较特殊,每个CPU会有2个L1 Cache。分别为指令高速缓存(Instruction Cache,简称iCache)和数据高速缓存(Data Cache,简称dCache)。
L2和L3一般不区分指令和数据,可以同时缓存指令和数据。
指令和数据来自哪里
CPU读取的指令和数据都存在内存中,当CPU读取cache时,如果发生了cache miss,就要从内存里读取。
那为什么就能从内存中读到指令和数据呢?它怎么知道哪是指令哪是数据呢?
其实这不是CPU的功劳,而是编译器已经做好了标记。指令和数据都存储在内存中的不同区域,区域的划分是编译连接时划分好的。
以linux run time memory image为例,static数据存储在bss, data段,stack存储局部变量和函数参数等,而代码段(即指令)存储在read-only segment,这个区域里面包括.init, .text, .rodata。
为什么要区分iCache和dCache
iCache的作用是缓存指令,dCache是缓存数据。为什么我们要把两种缓存分开呢?
一是出于性能的考量。
CPU在执行程序时,可以同时获取指令和数据,做到硬件上的并行,提升性能。
另外在CPU里,取指令单元与取数据单元是不一样的位置的,将iCache放在取指令单元(Instruction fetch)处,idCache放在取数据的单元(LSU)处,这样取指令和取数据都在距离自己最近的地方取,降低延迟。
二是指令和数据在缓存处理上不同。
首先指令一般不会被修改,所以iCache在硬件设计上是可以是只读的,这在一定程度上降低硬件设计的成本。
其次如果共用的缓存,那么在指令和数据同时访问L1的时候,添加仲裁单元的逻辑电路进行仲裁了,而cache分离,就完全不需要考虑仲裁了。
为什么不把各级Cache都进行分离iCache和dCache
区分指令和数据缓存也有一些缺点。
首先,分离式缓存不灵活,有时需要数据Cache大些,有时需要指令Cache大些,此时如果数据缓存和指令缓存分开设计,就无法动态调节Cache的比例。
在某些情况下可能会导致数据Cache已无法满足流水线需求但是指令缓存空闲,反之也有可能。而不分离的统一缓存可以动态调节。
其次,分离式缓存设计会占用逻辑电路的面积与设计难度,对于L1这种size比较小的采用分离式设计可以承受,但是对于更大的Cache全采用分离设计,设计难度和芯片面积都会加大。
目前芯片设计厂商需要取一个平衡,逐渐发展到现在L1分为指令缓存和数据缓存,而L2、L3是公用缓存的情况。
有过特例--Intel的安腾
那么历史上有没有将L2或者L3按照分离缓存设计的呢?有。
Intel发布过一款安腾CPU,Montecito处理器,其中L1,L2均采用数据和指令分离设计。其中L2的数据缓存为256KB,指令缓存为1MB,intel宣称可以减少L2的冲突,提高L2的吞吐量。后来Intel又恢复了L2使用统一缓存的结构。
分离的彻底
那有没有数据和指令完全分离的CPU设计呢,那不就是哈佛结构了吗?
我们知道冯·诺依曼和哈佛结构的本质区别就是指令空间和数据空间是否是一体的。冯·诺依曼结构数据空间和地址空间不分离,哈佛结构数据空间和地址空间是分离的。
典型的哈佛结构可以一边取指令一边取数据,指令和数据是完全分开的存储区域,有些处理器例如DSP根本没有cache,有多条数据总线(两条写,三条读)而只有一条指令总线。由于指令和数据存储区域分离,都不需要同样的字宽,这也是为什么会有16-bit的指令和8-bit的数据在同一个微控制器里。
现在处理器一般采用改进型的哈佛结构,或者说是哈佛结构和冯·诺依曼的结合,你还用过哪些不同结构的CPU呢?