CPU的缓存L1、L2、L3与缓存行填充
https://juejin.im/post/6844904071166427143
L1,L2,L3
指的都是CPU
的缓存,他们比内存快,但是很昂贵,所以用作缓存,CPU
查找数据的时候首先在L1,然后看L2
,如果还没有,就到内存查找一些服务器还有L3 Cache
,目的也是提高速度。
高速缓冲存储器Cache
是位于CPU
与内存之间的临时存储器,它的容量比内存小但交换速度快。在Cache
中的数据是内存中的一小部分,但这一小部分是短时间内CPU
即将访问的,当CPU
调用大量数据时,就可避开内存直接从Cache
中调用,从而加快读取速度。由此可见,在CPU
中加入Cache
是一种高效的解决方案,这样整个内存储器(Cache+内存)
就变成了既有Cache
的高速度,又有内存的大容量的存储系统了。Cache
对CPU
的性能影响很大,主要是因为CPU
的数据交换顺序和CPU
与Cache
间的带宽引起的。
高速缓存的工作原理
1. 读取顺序
CPU
要读取一个数据时,首先从Cache
中查找,如果找到就立即读取并送给CPU
处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU
处理,同时把这个数据所在的数据块调入Cache
中,可以使得以后对整块数据的读取都从Cache
中进行,不必再调用内存。
正是这样的读取机制使CPU
读取Cache
的命中率非常高(大多数CPU
可达90%左右),也就是说CPU
下一次要读取的数据90%都在Cache
中,只有大约10%需要从内存读取。这大大节省了CPU
直接读取内存的时间,也使CPU
读取数据时基本无需等待。总的来说,CPU
读取数据的顺序是先Cache后内存。
2. 缓存分类
前面是把Cache
作为一个整体来考虑的,现在要分类分析了。Intel
从Pentium
开始将Cache
分开,通常分为一级高速缓存L1
和二级高速缓存L2
。
在以往的观念中,L1 Cache
是集成在CPU
中的,被称为片内Cache
。在L1
中还分数据Cache(I-Cache)
和指令Cache(D-Cache)
。它们分别用来存放数据和执行这些数据的指令,而且两个Cache
可以同时被CPU
访问,减少了争用Cache所造成的冲突,提高了处理器效能。
在P4处理器中使用了一种先进的一级指令Cache——动态跟踪缓存。它直接和执行单元及动态跟踪引擎相连,通过动态跟踪引擎可以很快地找到所执行的指令,并且将指令的顺序存储在追踪缓存里,这样就减少了主执行循环的解码周期,提高了处理器的运算效率。
以前的L2 Cache
没集成在CPU
中,而在主板上或与CPU
集成在同一块电路板上,因此也被称为片外Cache
。但从PⅢ开始,由于工艺的提高L2 Cache
被集成在CPU
内核中,以相同于主频的速度工作,结束了L2 Cache
与CPU大差距分频的历史,使L2 Cache
与L1 Cache
在性能上平等,得到更高的传输速度。L2Cache
只存储数据,因此不分数据Cache
和指令Cache
。在CPU
核心不变化的情况下,增加L2 Cache
的容量能使性能提升,同一核心的CPU
高低端之分往往也是在L2 Cache
上做手脚,可见L2 Cache
的重要性。现在CPU
的L1 Cache
与L2 Cache
惟一区别在于读取顺序。
3. 读取命中率
CPU
在Cache
中找到有用的数据被称为命中,当Cache
中没有CPU
所需的数据时(这时称为未命中),CPU
才访问内存。从理论上讲,在一颗拥有2级Cache
的CPU
中,读取L1 Cache
的命中率为80%
。也就是说CPU
从L1 Cache
中找到的有用数据占数据总量的80%,剩下的20%从L2 Cache
读取。由于不能准确预测将要执行的数据,读取L2的命中率也在80%左右(从L2读到有用的数据占总数据的16%)。那么还有的数据就不得不从内存调用,但这已经是一个相当小的比例了。在一些高端领域的CPU
(像Intel
的Itanium
)中,我们常听到L3 Cache
,它是为读取L2 Cache
后未命中的数据设计的—种Cache
,在拥有L3 Cache
的CPU
中,只有约5%的数据需要从内存中调用,这进一步提高了CPU
的效率。
为了保证CPU
访问时有较高的命中率,Cache
中的内容应该按一定的算法替换。一种较常用的算法是“最近最少使用算法”(LRU算法
),它是将最近一段时间内最少被访问过的行淘汰出局。因此需要为每行设置一个计数器,LRU
算法是把命中行的计数器清零,其他各行计数器加1。当需要替换时淘汰行计数器计数值最大的数据行出局。这是一种高效、科学的算法,其计数器清零过程可以把一些频繁调用后再不需要的数据淘汰出Cache
,提高Cache
的利用率。
缓存行填充
CPU
访问内存时,并不是逐个字节访问,而是以字长为单位访问。比如32位的CPU
,字长为4字节,那么CPU
访问内存的单位也是4字节。
这么设计的目的,是减少CPU
访问内存的次数,加大CPU
访问内存的吞吐量。比如同样读取8个字节的数据,一次读取4个字节那么只需要读取2次。
我们来看看,编写程序时,变量在内存中是否按内存对齐的差异。有2个变量word1、word2
:
图如下:
我们假设CPU
以4字节
为单位读取内存。如果变量在内存中的布局按4字节对齐,那么读取a变量只需要读取一次内存,即word1
;读取b变量也只需要读取一次内存,即word2
。
而如果变量不做内存对齐,那么读取a变量也只需要读取一次内存,即word1
;但是读取b变量时,由于b变量跨越了2个word
,所以需要读取两次内存,分别读取word1
和word2
的值,然后将word1
偏移取后3个字节,word2
偏移取前1个字节,最后将它们做或操作,拼接得到b变量的值。
显然,内存对齐在某些情况下可以减少读取内存的次数以及一些运算,性能更高。
另外,由于内存对齐保证了读取b变量是单次操作,在多核环境下,原子性更容易保证。
但是内存对齐提升性能的同时,也需要付出相应的代价。由于变量与变量之间增加了填充,并没有存储真实有效的数据,所以占用的内存会更大。这也是一个典型的空间换时间的应用场景。
参考文章
作者:简栈文化
链接:https://juejin.im/post/6844904071166427143
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。