====== 第四章 高速缓冲存储器 ====== ===== 4.1 Cache的基本概念 ===== ==== 4.1.1 Cache的工作原理 ==== 高速缓冲存储器(Cache)是介于CPU和主存之间的高速小容量存储器,用于解决CPU与主存之间速度不匹配的矛盾。 **Cache的基本思想**: 根据程序的局部性原理,将CPU近期最可能访问的数据和指令从主存复制到Cache中。当CPU访问存储器时,首先访问Cache,如果命中则直接访问Cache;如果不命中,则从主存中读取数据并调入Cache。 **Cache的工作流程**: 1. CPU发出访问地址 2. 地址变换机构将主存地址转换为Cache地址 3. 判断数据是否在Cache中(命中判断) 4. 如果命中(Hit),直接从Cache读取数据或向Cache写入数据 5. 如果不命中(Miss),从主存读取数据,同时调入Cache(可能替换某些块),然后访问Cache **命中率与平均访问时间**: **命中率(H)**:CPU访问Cache命中的次数占总访问次数的比例 H = Nc / (Nc + Nm) 其中Nc为Cache命中次数,Nm为主存访问次数 **平均访问时间(Ta)**: Ta = H × Tc + (1-H) × (Tc + Tm) = Tc + (1-H) × Tm 其中Tc为Cache访问时间,Tm为主存访问时间 或者:Ta = H × Tc + (1-H) × Tm(假设命中时只访问Cache) **Cache的性能提升**: 设主存访问时间为100ns,Cache访问时间为10ns,命中率为95% 平均访问时间 = 0.95 × 10 + 0.05 × 100 = 9.5 + 5 = 14.5ns 加速比 = 100 / 14.5 ≈ 6.9 ==== 4.1.2 Cache的地址映射 ==== 主存和Cache都划分为大小相等的块(行)。为了将主存块装入Cache,需要建立地址映射关系。 **直接映射**: 每个主存块只能装入Cache中唯一确定的位置。 映射公式:Cache行号 = 主存块号 mod Cache总行数 地址划分: | 主存标记 | Cache行号 | 块内地址 | 优点: - 实现简单,只需比较一次标记 - 硬件成本低 缺点: - 冲突率高,即使Cache有空闲也可能发生冲突 - 可能导致频繁的替换,命中率下降 **全相联映射**: 主存块可以装入Cache的任意位置。 地址划分: | 主存标记 | 块内地址 | 优点: - 冲突率低,命中率高 - 空间利用率最高 缺点: - 查找需要比较所有Cache行的标记 - 硬件复杂,成本高 - 速度慢 **组相联映射**: 直接映射和全相联映射的折中方案。Cache分成若干组,每组包含若干行。主存块映射到特定组,但可以在组内任意位置。 映射公式:组号 = 主存块号 mod 组数 地址划分: | 主存标记 | 组号 | 块内地址 | K路组相联:每组包含K个Cache行 优点: - 冲突率低于直接映射 - 查找范围缩小到组内,硬件复杂度适中 - 是实际应用中最常用的映射方式 **三种映射方式的比较**: **图4-1:Cache三种映射方式示意图** {{https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Cache_associativity_patterns.svg/800px-Cache_associativity_patterns.svg.png?direct|Cache映射方式:直接映射、组相联、全相联|600}} | 映射方式 | 查找复杂度 | 命中率 | 硬件成本 | |----------|------------|--------|----------| | 直接映射 | 最低 | 较低 | 最低 | | 全相联映射 | 最高 | 最高 | 最高 | | 组相联映射 | 中等 | 较高 | 中等 | ===== 4.2 Cache的替换算法与写策略 ===== ==== 4.2.1 替换算法 ==== 当Cache已满且需要装入新块时,需要选择一个块替换出去。常用的替换算法有: **随机法(Random)**: 随机选择要替换的块。 - 优点:实现简单 - 缺点:未考虑访问局部性,命中率可能较低 **先进先出法(FIFO)**: 替换最早装入Cache的块。 - 优点:实现简单,符合公平原则 - 缺点:未考虑访问频率,可能替换经常访问的块 **最近最少使用法(LRU)**: 替换最久未被访问的块。 - 优点:符合局部性原理,命中率较高 - 缺点:硬件实现复杂,需要记录访问历史 LRU的近似实现: - 为每行设置使用位,访问时置1 - 需要替换时,按顺序找第一个为0的项 - 所有位都为1时,全部清零重新开始 **最不经常使用法(LFU)**: 替换访问次数最少的块。 - 需要计数器记录访问次数 - 新装入的块可能很快被淘汰 ==== 4.2.2 写策略 ==== Cache的写操作需要保持Cache和主存的一致性,常用的写策略有: **写直达法(Write Through)**: 写操作时同时写入Cache和主存。 - 优点:数据一致性好,实现简单 - 缺点:写操作速度慢(需要等待主存) - 优化:使用写缓冲区,CPU只需写入Cache和缓冲区,由缓冲区异步写入主存 **写回法(Write Back)**: 写操作时只写入Cache,当Cache块被替换时才写回主存。 - 优点:写操作速度快,减少主存访问次数 - 缺点:数据一致性差,需要设置脏位(修改位)标记是否修改过 - 脏位:1表示修改过,替换时需写回;0表示未修改,可直接丢弃 **写策略的比较**: | 特性 | 写直达 | 写回 | |------|--------|------| | 可靠性 | 高 | 低 | | 与主存通信量 | 多 | 少 | | 控制复杂度 | 简单 | 复杂 | | 应用 | 单处理器 | 多处理器 | **写不命中时的策略**: **写分配法(Write Allocate)**: 写不命中时,先将数据块从主存装入Cache,然后再写入Cache。 通常与写回法配合使用。 **非写分配法(No Write Allocate)**: 写不命中时,直接写入主存,不调入Cache。 通常与写直达法配合使用。 ===== 4.3 Cache的改进技术 ===== ==== 4.3.1 多级Cache ==== 为了进一步提高性能,现代计算机普遍采用多级Cache结构。 **两级Cache结构**: - L1 Cache:位于CPU芯片内部,速度最快,容量最小(通常32-64KB) - L2 Cache:可以是片内或片外,容量较大(通常256KB-几MB) **三级Cache结构**: - L1 Cache:分指令Cache和数据Cache(哈佛结构),容量16-64KB - L2 Cache:统一的Cache,容量256KB-512KB - L3 Cache:多核心共享,容量几MB-几十MB **多级Cache的平均访问时间**: 对于两级Cache: Ta = H1 × T1 + (1-H1) × [H2 × T2 + (1-H2) × Tm] 其中H1、H2分别为L1、L2的命中率,T1、T2为访问时间,Tm为主存访问时间 ==== 4.3.2 分离Cache与统一Cache ==== **分离Cache(哈佛结构)**: 指令和数据分别存放在不同的Cache中(ICache和DCache)。 - 优点:减少指令和数据访问冲突,支持并行访问,适合流水线 - 缺点:不能动态调整指令和数据的空间分配 **统一Cache**: 指令和数据共享同一个Cache。 - 优点:空间利用率高,可以根据需要动态分配 - 缺点:指令和数据可能产生冲突 现代处理器通常在L1使用分离Cache,L2及以上使用统一Cache。 ==== 4.3.3 预取技术 ==== **预取的概念**: 在处理器实际需要数据之前,提前将其装入Cache,减少Cache不命中的延迟。 **预取方式**: 1. **硬件预取**:由硬件自动识别访问模式进行预取 - 顺序预取:预取下一个块 - 跨步预取:识别固定间隔的访问模式 2. **软件预取**:由编译器插入预取指令 - 非阻塞预取指令:不阻塞处理器执行 - 可以精确控制预取时机 **预取的挑战**: - 预取过早:数据可能在需要前就被替换 - 预取过晚:无法隐藏延迟 - 无用预取:浪费带宽和能量,可能替换有用数据 ==== 4.3.4 虚拟Cache ==== **虚拟Cache的概念**: 使用虚拟地址而非物理地址来访问Cache,避免地址转换的开销。 **优点**: - 命中时无需进行地址转换,速度快 - 减少TLB的压力 **问题**: - 别名问题:不同虚拟地址可能映射到同一物理地址 - 同义问题:同一虚拟地址在不同进程中的含义不同 - 进程切换时需要清空Cache **解决方法**: - 使用物理标记的虚拟Cache - 增加进程标识符(PID)区分不同进程 - 限制Cache索引使用页内偏移(避免别名) ===== 4.4 Cache的性能优化 ===== ==== 4.4.1 影响Cache性能的因素 ==== **Cache容量**: - 容量越大,命中率越高 - 容量越大,访问时间越长,成本越高 - 需要在性能和成本之间权衡 **块大小**: - 块越大,空间局部性利用好 - 块过大可能增加不命中惩罚,减少Cache行数 - 存在一个最优块大小(通常为32-128字节) **相联度**: - 相联度越高,冲突不命中越少 - 相联度越高,查找时间越长,硬件成本越高 - 实际常用2路、4路、8路组相联 **不命中的类型**: 1. **强制性不命中(冷启动不命中)**: 第一次访问某个块,不可避免 减少方法:预取、增大块大小 2. **容量不命中**: Cache容量不足以容纳程序所需的所有块 减少方法:增大Cache容量 3. **冲突不命中**: 多个块映射到同一Cache位置 减少方法:提高相联度、增大Cache容量 ==== 4.4.2 软件优化技术 ==== **程序优化**: 1. **循环交换**: 改变循环嵌套顺序,改善空间局部性 ```c // 优化前:按列访问,Cache不友好 for (i=0; i