Cache Kownledge -- Cache层次结构 & Cache一致性

一、缓存的体系结构

1. Cache层次结构

计算机系统中缓存层次结构通常由多个级别的缓存组成,这些级别包括L1缓存、L2缓存和L3缓存。每个级别的缓存都有不同的特性,例如容量、访问速度和成本。层次化缓存结构的设计旨在利用不同级别缓存的优势,通过提供更小、更快的缓存来提高数据访问速度,并通过较大、较慢的缓存提供更大的存储容量。这有助于平衡性能和成本。

以下是一般的层次化缓存结构图:

2. CPU读取方式

  • 程序执行时,会先将内存中的数据加载到共享的 L3 Cache 中,再加载到每个核心独有的 L2 Cache,最后进入到最快的 L1 Cache,之后才会被 CPU 读取。
  • CPU读取数据时:
    1. 发起读取请求(Fetch)

      • CPU 从程序计数器(Program Counter,PC)中获取下一条指令的地址。
      • 将该地址发送到 L1 指令缓存。
    2. L1 指令缓存检查(L1 I-cache Check)

      • 如果指令在 L1 指令缓存中命中(Cache Hit),则直接从 L1 指令缓存中读取指令。
      • 如果没有命中(Cache Miss),则继续到下一级缓存。
    3. L2 缓存检查(L2 Cache Check)

      • 如果指令在 L2 缓存中命中,从 L2 缓存中读取指令,并更新 L1 指令缓存。
      • 如果没有命中,则继续到下一级缓存。
    4. L3 缓存检查(L3 Cache Check)

      • 如果指令在 L3 缓存中命中,从 L3 缓存中读取指令,并更新 L2 和 L1 指令缓存。
      • 如果没有命中,则继续到主内存。
    5. 主内存访问(Main Memory Access)

      • 如果指令在主内存中,从主内存中读取指令,并更新 L3、L2 和 L1 指令缓存。
    6. 指令解码(Decode)

      • CPU 对获取的指令进行解码,识别出具体的操作。
    7. 执行指令(Execute)

      • CPU 根据解码结果执行相应的操作,可能包括读取或写入数据。

以下是Cache结构图:

3. 映射方式

关联性的选择取决于系统的设计目标和性能需求。直接映射简单,但冲突可能较多;组相联映射在性能和成本之间取得平衡;全相联映射提供最大的关联性,但通常需要更多的硬件资源。不同的应用场景可能需要不同类型的缓存关联性。

  1. 直接映射(Direct Mapping):

    • 原理:主要原理是将主存中的每个块映射到缓存中的一个特定缓存行。具体来说,一个主存块只能映射到缓存的一个固定位置,而不是可以在多个位置中选择。这个位置通常是通过主存块的地址经过散列函数计算得到的。
    • 优点:简单、易于实现。
    • 缺点:容易发生冲突,当多个主存块映射到同一个缓存行时,可能导致缓存冲突。
    • 特点
      • 地址映射:主存中的每个块被映射到缓存中唯一的一个位置。这个映射关系通常通过取主存块地址的某几位来确定,这些位称为索引位。

      • 缓存行结构:缓存被划分为多个缓存行,每个缓存行对应主存中的一个块。每个缓存行包含两个部分:存储数据的数据块和用于标记的标记字段。

      • 替换策略:如果一个主存块要存储到缓存的位置已经被占用,就需要进行替换。直接映射通常采用最简单的替换策略——直接替换,即将新块直接替换掉当前位置上的块。

      • 缓存访问:当 CPU 请求某个主存块时,首先计算该块应该映射到的缓存位置。然后,检查该位置上的标记字段,如果标记字段与请求的块匹配,说明缓存命中,可以直接返回数据。如果不匹配,需要将当前位置的数据块替换,并将新的块加载到该位置,同时更新标记字段。

  2. 组相联映射(Set-Associative Mapping):

    • 原理:介于直接映射和全相联映射之间的缓存映射方式。在组相联映射中,缓存被分成多个组,每个组包含多个缓存行。与直接映射不同的是,一个主存块可以映射到一个组中的多个位置,而不是只能映射到一个位置
    • 优点:缓解了直接映射中的冲突问题,同时相对简单。
    • 缺点:仍可能发生冲突,但冲突的机会较少。
    • 特点:
      • 地址映射:主存块的地址被映射到缓存的组中。组的数量和每个组中的缓存行数量由缓存的设计决定。映射关系通常通过取主存块地址的某几位来确定,这些位称为索引位。

      • 组内映射:每个组内的缓存行之间采用直接映射的方式。即,主存块可以映射到组内的任意一个位置。这个位置通常是通过主存块地址的另一部分来确定,称为组内偏移位。

      • 替换策略:如果一个主存块要存储到组中的某个位置已经被占用,就需要进行替换。组相联映射通常采用集合中的直接替换策略,即将新块直接替换掉当前集合中的某个位置上的块。

      • 缓存访问:当 CPU 请求某个主存块时,首先计算该块应该映射到的组。然后,在组内进行直接映射的缓存访问,检查是否命中。如果命中,可以直接返回数据。如果未命中,则根据替换策略选择一个位置进行替换,并将新的块加载到该位置。

  3. 全相联映射(Fully Associative Mapping):

    • 原理:其中主存中的任何块都可以映射到缓存中的任何位置,而不受任何组的限制。每个缓存位置都可以存储任意主存块。
    • 优点:最大程度地减少了冲突,可以更灵活地利用缓存。
    • 缺点:实现相对复杂,成本较高。
    • 特点:
      • 地址映射:主存块的地址可以直接映射到缓存中的任意一个位置。这意味着不需要考虑组的概念,每个位置都可以存储任何主存块。映射关系通常通过主存块地址的某些位来确定,这些位被称为索引位。

      • 替换策略:如果一个主存块要存储到缓存中的某个位置已经被占用,就需要进行替换。全相联映射通常采用LRU(Least Recently Used)或FIFO(First In, First Out)等替换策略,以确定哪个位置应该被替换。

      • 缓存访问:当 CPU 请求某个主存块时,直接在缓存中进行全相联映射的查找。检查每个位置,看是否存储了请求的主存块。如果命中,可以直接返回数据。如果未命中,则根据替换策略选择一个位置进行替换,并将新的块加载到该位置。

4. 补充知识点

1.替换策略LRU(Least Recently Used)和FIFO(First In, First Out)

LRU(Least Recently Used)和FIFO(First In, First Out)是两种常见的缓存替换策略,用于确定在需要替换缓存行时选择哪个。

  1. LRU(Least Recently Used): LRU是一种基于最近访问历史的替换策略。它假定最近被访问的缓存行是最有可能在不久的将来被再次访问的。当需要替换缓存行时,LRU选择最近未被使用的缓存行进行替换。为了实现LRU,系统通常需要维护一个访问历史记录或者使用一些算法来估计最近的访问情况。

  2. FIFO(First In, First Out): FIFO是一种简单而直观的替换策略。它基于先进先出的原则,即最先进入缓存的行将被最先替换。当需要替换缓存行时,选择最早进入的缓存行进行替换。FIFO的实现通常涉及一个队列结构,新的缓存行进入队列的尾部,而替换时选择队列头部的缓存行。

比较:

  • LRU的优点: LRU更智能,能够更好地适应程序的访问模式,减少缓存行的冷启动。
  • FIFO的优点: FIFO实现简单,不需要额外的硬件开销,并且在某些场景下表现良好。

2.为什么要将这些数据也存储到更高级别的缓存中?

将数据存储到更高级别的缓存中有几个主要原因:

  1. 提高访问速度: 高级别的缓存(比如 L1 缓存)通常比低级别的缓存(比如 L2 或 L3 缓存)响应速度更快。将数据存储到更高级别的缓存中,可以确保下次对同一数据的访问可以更快地获取到,从而提高程序的执行效率。

  2. 减少内存访问延迟: 如果数据在更高级别的缓存中已经存在,那么就不需要从主内存中重新获取这些数据,从而减少了内存访问的延迟。内存访问通常是相对较慢的操作,因此通过在缓存层次结构中存储数据,可以加速对这些数据的访问。

  3. 缓解总线和内存带宽压力: 直接从主内存读取大量的数据可能导致总线和内存带宽的压力增加。通过在高级别缓存中存储数据,可以减少对主内存的频繁访问,从而减轻了系统的总线和内存带宽负担。

  4. 提高并发性: 缓存的使用可以提高对多个数据块的并发访问能力。如果多个处理器核心或执行单元需要访问相同的数据,这些数据在高级别的缓存中的共享可以减少对主内存的竞争,提高并发性能。

3.数据更新到L1 Cache的处理方式

数据从 L2 或 L3 缓存读取回 L1 缓存时,有两种常见的方式来处理:

  1. 写回(Write Back):在写回策略下,当从 L2 或 L3 缓存读取的数据被更新时,更新的数据首先被写入 L1 缓存,而不是立即写回到更低级别的缓存或主内存。这可以提高对相同数据的多次访问的速度。只有在需要淘汰(替换)缓存行时,才会将更新的数据写回到更低级别的缓存或主内存。

  2. 写直达(Write Through):在写直达策略下,从 L2 或 L3 缓存读取的数据被更新后,会立即写回到更低级别的缓存或主内存。这样可以确保所有级别的缓存和主内存中的数据保持一致性,但可能会增加写操作的延迟。

4.当处理器访问一块数据时,它很可能再次访问这块数据或者访问此存储位置附近的数据,为什么要访问附件的数据?

访问附近的数据是基于程序局部性原理,主要包括时间局部性和空间局部性。这两种局部性是指在一段时间内,程序对某些数据或指令的访问具有集中性。

  1. 时间局部性:表示如果程序中的某个数据被访问,那么在不久的将来,该数据可能再次被访问。这体现了循环结构和迭代算法的特点。通过提前将访问的数据放入缓存,可以避免频繁地从主内存中读取相同的数据。

  2. 空间局部性:表示程序访问的数据通常与最近访问的数据在内存中的位置相邻。例如,数组、数据结构等通常在内存中是连续存储的。通过预取附近的数据,可以充分利用这种空间局部性,减少缓存未命中的可能性。

所以,通过提前加载附近的数据,可以利用程序访问数据的集中性,减少缓存未命中的情况,提高整体程序执行效率。这种预取数据的机制有助于克服存储层次结构(如Cache、内存)速度和容量之间的差异,提高计算机系统的性能。

二、Cache一致性

1. Cache一致性是什么?

缓存一致性是指多个处理器或者核心之间共享的数据在各自的缓存中保持一致的状态。

在多处理器系统中,每个处理器都有自己的缓存用于加速对内存的访问,但这会引入一些挑战,因为一个处理器对共享数据的修改可能不会立即被其他处理器看到,导致数据不一致的情况。

2. 保持Cache一致性

1)Cache一致性可能出现的问题:

  • 写命令重排序:处理器在执行写操作时,可能会重新排列写入内存的顺序,使得写入的顺序与程序中的顺序不一致。这种重排序可能发生在处理器的流水线执行阶段,其中写入内存的指令被分成多个阶段并可能与其他指令交错执行。

    1)写命令重排序的原因主要有两点:

    1. 流水线执行:处理器通常采用流水线执行的方式,将指令的执行过程划分为多个阶段(如取指、译码、执行、写回等)。在流水线执行过程中,写入内存的指令可能在流水线中的不同阶段被执行,因此它们的写入顺序可能会发生变化。

    2. 数据相关性:如果程序中的两个写操作之间没有数据相关性(例如,它们写入不同的内存位置),处理器可能会重排序这两个写操作,以提高整体执行性能。

    2)写命令重排序可能导致的问题包括:

    • 数据依赖性错误:如果程序中的两个写操作之间存在数据依赖性,并且处理器重排序了这两个写操作,可能导致错误的结果。

    • 多处理器一致性问题:在多处理器系统中,一个处理器的写入可能在另一个处理器看来发生在其他写入之前,导致一致性问题。

    为了解决写命令重排序可能带来的问题,现代处理器通常会使用写缓冲(write buffer)等机制来保持写入的顺序,并确保写操作按照程序中的顺序提交到内存。此外,一致性模型和缓存一致性协议也是确保多处理器系统中写入顺序一致的关键。在软件层面,程序员可以使用内存屏障(memory barrier)等同步指令来显式地控制写入操作的顺序。

  • 写命令延迟:处理器可能会延迟写回缓存,而其他处理器读取相同位置的数据时,可能读到的是旧值。这种延迟可能由于处理器内部流水线的特性、缓存的工作方式以及内存层次结构等因素引起。

    1)写命令延迟可能出现的原因包括:

    • 缓存未命中:如果写入的内存地址在缓存中未命中,处理器需要从更低层次的内存中读取相应的数据块,这会引入读取延迟。

    • 写缓冲满:当写缓冲已满时,处理器可能需要等待写缓冲中的某些数据被写回内存,以腾出空间接收新的写入请求。这可能导致写命令的等待时间。

    • 内存层次结构:如果写入的数据需要经过多个层次的缓存和主内存,每个层次的访问延迟都会对写命令的总延迟产生影响。

    为了减少写命令延迟,处理器设计中采用了各种优化技术,包括写缓冲的使用、多层次缓存的设计、写回策略的优化等。此外,内存层次结构的设计和一致性协议的选择也对写命令的延迟有影响。

  • 写命令合并:处理器可能将多个写命令合并为一个,这可能导致一些写入被跳过。

    • 写命令合并的实现通常依赖于处理器内部的写缓冲和缓存系统。当处理器执行多个相邻的写指令时,这些写指令的写入操作可能会被暂时存储在写缓冲中。处理器在决定将数据写回内存时,可以选择将多个写缓冲中的写入操作合并为一个更大的写操作,然后一次性地将合并后的数据块写回内存。

2)如何Cache一致性?

为了解决这些问题,多处理器系统通常采用一致性协议,其中包括以下几种常见的协议:

  • MESI 协议: MESI(Modified, Exclusive, Shared, Invalid)是一种常见的缓存一致性协议。每个缓存行有四种状态:Modified(已修改)、Exclusive(独占)、Shared(共享)、Invalid(无效)。处理器通过在缓存行状态之间进行转换来保持一致性。

    1. Modified(已修改):表示该缓存行的数据被当前处理器修改过,并且和主内存中的数据不一致。如果其他处理器要读取这个数据,当前处理器需要先将数据写回主内存,然后其他处理器可以从主内存中读取。
    2. Exclusive(独占):表示当前处理器是该缓存行的唯一拥有者,且数据和主内存中的数据一致。其他处理器可以直接从当前处理器的缓存中读取数据,而不需要访问主内存。
    3. Shared(共享):表示多个处理器共享同一份数据,且数据和主内存中的数据一致。其他处理器也可以从主内存中读取这个数据,而不需要等待当前处理器。
    4. Invalid(无效):表示当前缓存行的数据无效,不可用。这可能是因为其他处理器修改了这个数据,导致当前处理器的缓存失效。需要重新从主内存中读取。
  • MOESI 协议: MOESI 是对 MESI 协议的扩展,引入了 Owned(所有权)状态。Owned 状态表示该处理器具有对共享数据的所有权,其他处理器可以读取但不能修改。

  • MESIF 协议: MESIF 是对 MESI 协议的改进,引入了 Forward(转发)状态。Forward 状态表示该缓存行的数据可能已被其他缓存修改,但是该缓存仍然具有最新的数据,可以转发给其他处理器。

这些协议通过在缓存之间进行通信和协调,确保对共享数据的访问保持一致,避免了数据不一致的问题。在硬件层面,这可能涉及缓存一致性的硬件机制,例如嗅探器(snoop controller)来检测其他处理器对数据的修改。在软件层面,程序员也需要考虑缓存一致性,并可能使用一些同步机制来确保正确的数据访问顺序。

  • 39
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值