目录
1.程序访问的局部性原理
局部性原理(Principle of Locality)是计算机科学中一条重要的原则,描述了计算机程序在执行时访问数据和指令的行为模式。局部性原理有两种主要形式:时间局部性和空间局部性。为了更好地理解这两个概念,我们可以通过一个简单的例子来说明。
假设你有一个简单的程序,用来计算一个大小为1000的整数数组的所有元素之和,并且数组中的每个元素都进行了一次乘法操作。
int sum = 0;
int array[1000];
// 初始化数组
for (int i = 0; i < 1000; i++) {
array[i] = i * 2;
}
// 计算数组的和
for (int i = 0; i < 1000; i++) {
sum += array[i];
}
时间局部性
时间局部性指的是如果一个程序在某一时刻访问了某个数据项,那么在不久的将来它可能会再次访问这个数据项。
在上面的代码中,sum
变量就是一个体现时间局部性的例子。在计算数组元素的和时,sum
变量在每次迭代时都会被频繁地访问并更新。由于sum
变量的值在每次迭代中都被反复读取和修改,所以它展示了很强的时间局部性。
空间局部性
空间局部性指的是如果一个程序在某一时刻访问了某个数据项,那么它很可能会访问与这个数据项相邻的数据项。意思是如果一个程序访问了某个数据项,那么在不久的将来可能不会再次访问这个数据项。
在上面的代码中,array
数组的访问就是一个体现空间局部性的例子。当我们在循环中依次访问array[i]
时,由于数组在内存中是连续存储的,所以访问array[i]
之后,程序很可能会访问array[i+1]
。这就是空间局部性的体现。
局部性原理在计算机系统中有很多重要的应用,尤其是在缓存系统中。例如,当程序频繁访问相同的数据或相邻的数据时,缓存可以通过保留这些数据来减少访问主内存的次数,从而加快程序的执行速度。
局部性原理说明了程序在访问数据和指令时的行为规律。时间局部性强调了频繁访问相同数据的趋势,而空间局部性则强调了访问相邻数据的趋势。这两个特性使得计算机系统能够更高效地管理内存和缓存,从而提升程序的执行性能。
2.Cache的基本工作原理
基于局部性原理,不难想到,可以把CPU目前访问的地址“周围”的部分数据放到Cache中。如何界定“周围”?
将主存的 存储空间“分块”
如:每 1KB为一块。主存与Cache之间以“块”为单位进行数据交换
每次被访问的数据块一定会被立即调入Cache.
注:
操作系统中,通常将主存中的“一个块”也称为“一个 页/页面/页框”
Cache中的“块”也称为“行 ”。
性能分析
设tc为访问一次Cache所需时间,tm为访问一次主存所需时间
命中率H:CPU欲访问的信息已在Cache中的比率
缺失(未命中)率:M=1-H
先访问Cache,若Cache未命中再访问主存
Cache-主存系统的平均访问时间t为
t = Htc + (1-H)(tc + tm)
同时访问 Cache和主存,若cache命中则立即停止访问主存
Cache-主存系统的平均访问时间t为
t=Htc + (1-H)tm
示例:
假设Cache的速度是主存的5倍,且Cache的命中率为95%,则采用Cache后存储器性能提高多少?
若Cache和主存同时被访问,若Cache命中则中断访问主存:
设Cache的存取周期为t,则主存的存取周期为5t
若Cache和主存同时访问,命中时访问时间为t,未命中时访问时间为5t平均访问时间为 0.95xt+0.05x5t=1.2t
故性能为原来的5t/1.2t≈4.17倍
若先访问cache再访问主存:
命中时访问时间为t;
未命中时访问时间为t+5t
平均访问时间为0.95xt+0.05x6t=1.25t
故性能为原来的5t/1.25t=4倍
3.Cache和主存的映射方式
如何区分Cache与主存的数据块对应关系?
全相联映射
主存块可以放在Cache的任意位置
直接映射
每个主存块只能放到一个特定的位置:Cache块号=主存块号%Cache总块数
组相联映射
Cache块分为若干组,每个主存块可放到特定分组中的任意一个位置组号=主存块号%分组数
对比
名字 | 如何映射 | 主存地址结构 | 优点 | 缺点 |
---|---|---|---|---|
全相联映射 | 主存块可以放到Cache的任意位置 | 标记(整个主存块号)+块内地址 | Cache存储空间利用充分,命中率高 | 查找“标记”最慢,有可能需要对比所有行的标记 |
直接映射 | 主存块只能放到特定的某个Cache行, 行号=主存块号%总行数 | 标记(主存块号前几位)+行号(主存块号末几位)+块内地址 | 对于任意一个地址,只需对比一个“标记”,速度最快 | Cache存储空间利用不充分,命中率低 |
组相联映射 | 主存块可以放到特定分组中的任意位置,所属组号=主存块号%总组数 | 标记(主存块号前几位)+组号(主存块号末几位)+块内地址 | 另外两种方式的折中,综合效果较好 |
4.Cache中主存块的替换算法
Cache 很小,主存很大。如果Cache满了怎么办?
Rand
随机算法,Cache满后随机换出一个内存块。
设总共有4个Cache块,初始整个Cache为空。采用全相联映射,依次访问主存块{1,2,3,4,1,2,5,1,2,3,4,5}
随机算法--实现简单,但完全没考虑局部性原理,命中率低,实际效果很不稳定
FIFO
先入先出,Cache满后将最先调入的内存块换出。
设总共有4个Cache块,初始整个Cache为空。采用全相联映射,依次访问主存块{1,2,3,4,1,2,5,1,2,3,4,5}
先进先出算法--实现简单,最开始按#0#1#2#3放入Cache,之后轮流替换#0#1#2#3FIFO依然没考虑局部性原理最先被调入Cache的块也有可能是被频繁访问的
LRU
近期最少使用算法(LRU,Least Recently Used )--为每一个Cache块设置一个“计数器”,用于记录每个Cache块已经有多久没被访问了。当Cache满后替换“计数器”最大的
设总共有4个cache块,初始整个cache为空。采用全相联映射,依次访问主存块(1,2,3,4,1,2,5,1,2,3,4,5}
①命中时,所命中的行的计数器清零,比其低的计数器加1,其余不变
②未命中且还有空闲行时,新装入的行的计数器置0,其余非空闲行全加1
③未命中且无空闲行时,计数值最大的行的信息块被淘汰,新装行的块的计数器置0,其余全加1。
LRU算法--基于“局部性原理”,近期被访问过的主存块,在不久的将来也很有可能被再次访问,因此淘汰最久没被访问过的块是合理的。LRU算法的实际运行效果优秀,Cache命中率高。若被频繁访问的主存块数量Cache行的数量,则有可能发生“抖动”如:{1,2,3,4,5,1,2,3,4,5,1,2...}
LFU
最不经常使用算法(LFU, Least FreguentlyUsed)--为每一个Cache块设置一个“计数器”,用于记录每个Cache块被访问过几次。当Cache满后替换“计数器”最小的
设总共有4个Cache块,初始整个cache为空。采用全相联映射,依次访问主存块(1,2,3,4,1,2,5,1,2.3.4.5}
LFU算法--曾经被经常访问的主存块在未来不一定会用到(如:微信视频聊天相关的块)并没有很好地遵循局部性原理,因此实际运行效果不如LRU
对比
算法 | 思想 | 特点 |
---|---|---|
随机算法(RAND) | 随便选一个主存块替换 | 过于 Freestyle,效果很差 |
先进先出算法(FIFO) | 优先替换最先被调入Cache的主存块 | 不遵循局部性原理,效果差 |
近期最少使用(LRU) | 将最久没有被访问过的主存块替换。每个Cache行设置一个“计数器”,用于记录多久没被访问 | 基于“局部性原理”,近期被访问过的主存块,在不久的将来也很有可能被再次访问,因此淘汰最久没被访问过的块是合理的。LRU算法的实际运行效果优秀,Cache命中率高。 |
最不经常使用(LFU) | 将被访问次数最少的主存块替换。每个Cache行设置一个“计数器”,用于记录被访问过多少次 | 曾经被经常访问的主存块在未来不一定会用到,LFU实际运行效果不好 |
5.Cache写策略
CPU修改了Cache中的数据副本,如何确保主存中数据母本的一性?
写命中时
全写法
当CPU对Cache写命中时,必须把数据同时写入Cache和主存,一般使用写缓冲(write buffer)
写回法
当CPU对Cache写命中时,只修改Cache的内容,而不立即写入主存,只有当此块被换出时才写回主存
写不命中时
写分配法
当CPU对Cache写不命中时,把主存中的块调入Cache,在Cache中修改。通常搭配写回法使用
非写分配法
当CPU对Cache写不命中时只写入主存,不调入Cache。通常搭配全写法使用。
多级Cache
现代计算机通常采用多级Cache结构,各级Cache间常采用“全写法+非写分配法”,Cache和主存间常采用“写回法+写分配法”
现代计算机常采用多级Cache:
离CPU越近的速度越快,容量越小
离CPU越远的速度越慢,容量越大