一直以来,CPU 总是比内存快,并且差距越拉越大。
原因是,每当有可能在单片芯片上放置更多的电路时,CPU 的设计者总是用它们来事先流水和超标量运算,时 CPU 变得更快。
而内存的设计者却利用这些技术来提高芯片的容量,而不是速度。
这就使两者的速度差距越来越大。
内存速度比 CPU 慢很多,这不是技术问题,事实上,完全可以造出和 CPU 一样快的内存,但是,如果要求全速运行,这种内存只能内置在 CPU 芯片中,因为数据经过总线就太慢了。
在 CPU 中放置大容量内存将使 CPU 变大,价格提高,即使价格不成问题,CPU 芯片的大小也有一个限度。
这样,我们就需要在小容量高速内存和大容量低速内存之间做选择。当然,我们更想要大容量高速内存🤗。
有意思的是,从技术上已经可能将小容量的高速内存和大容量的低速内存组合在一起,以适中的价格得到速度和高速内存差别不大的大容量内存。
小容量高速内存称为高速缓存(cache,来自法语 cacher,意思是隐藏)。
高速缓存的工作原理十分简单:把读取频度最高的内存内容保存在高速缓存中。CPU 需要读入内存内容时,首先在高速缓存中查找。只有在高速缓存中找不到时,才去读内存中的内容。
人们认识到,程序并不是完全随机地访问内存。
程序地访存行为具有时间局部性,也就是说上一次访问了该数据,下一次很大程度上还会继续访问。同时还具有空间局部性,也就是如果访问地址 A 上的数据,那么很有可能下一次会访问地址 A±1 上的数据,也就是地址 A 附近地址的数据。
有了这两个前提,缓存的命中率可以保持很高水平。
对于时间局部性,高速缓存的命中率可以保持在很高的级别,比如 95% 以上。
对于空间局部性,缓存控制器可以采用预读的方式,一次性从内存中提取比 CPU 发出的访问请求更多的数据上来,比如 CPU 访问地址 A,缓存控制器可以从内存中提取 A、A+1、A+2 等连续地址的内容,目前主流 CPU 一次从内存提取到缓存的数据大小是 64 字节。
从成本和面积考虑,缓存做不大。另外一个考虑则是容量太大的话,当 CPU 访问某个地址的时候,缓存控制器在缓存中搜索该数据是否已经位于缓存时的速度会变慢,所以也不能做的太大。
人们决定使用分级搜索的方式,将缓存分成多个层级:L1 缓存容量最小,电路规模小,运行频率就可以更高,搜索起来最快,然后时 L2、L3 等,以及最后一级缓存(Last Level Cache,LLC),目前主流产品的 LLC 一般就是 L3。这些缓存层级一级比一级容量大,搜索速度逐渐减慢。
要充分理解的一点是,缓存是不可以被寻址的。也就是说,CPU 如果有生命的话,它是看不到缓存的存在的,程序员也看不到,即不存在类似这样的代码:“Load 地址A L1缓存”、“Stor L2缓存中的某行数据 地址A”。这就是说,程序员无法细粒度地控制缓存,但是可以粗粒度地操作缓存。比如,实际中存在类似这样地代码:“Flush Cache”(将已经写入缓存但是还没有写入内存的数据写入内存),再比如 “Prefetch 参数”(根据参数将数据预读入缓存)。