AMD 的微标记 L1 数据缓存是如何访问的?

Yuj*_*jie 2 x86 caching cpu-architecture cpu-cache amd-processor

我正在学习AMD处理器L1缓存的访问过程。但我反复看了AMD的手册,还是看不懂。

我对Intel的L1数据缓存的理解是:
L1缓存是虚拟索引和物理标记的。因此,利用虚拟地址的索引位来查找对应的缓存集合,最终根据标记确定缓存集合中的哪条缓存行。
(Intel 使他们的 L1d 缓存足够关联且足够小,索引位仅来自页内偏移,与物理地址相同。因此,他们获得了 VIPT 的速度,没有任何别名问题,表现得像 PIPT .)

但AMD使用了一种新方法。在 Zen 1 中,它们有一个 32 KB、8 路组关联 L1d 缓存,该缓存(与 64KB 4 路 L1i 不同)足够小,可以在没有微标签的情况下避免别名问题。
来自AMD 2017年软件优化手册,第2.6.2.2节“AMD系列17h处理器的微架构”(Zen 1):

L1 数据缓存标签包含一个基于线性地址的微标签 (utag),它使用最初用于访问缓存行的线性地址来标记每个缓存行。负载使用此 utag 来确定使用其线性地址读取缓存的哪条路,该线性地址在通过 TLB 确定负载的物理地址之前可用。utag 是负载线性地址的哈希值。这种基于线性地址的查找使得能够在读取高速缓存数据之前非常准确地预测高速缓存行的定位方式。这允许负载仅读取单个缓存方式,而不是全部 8 个。这可以节省电量并减少存储体冲突。

utag 有可能在两个方向上都是错误的:它可以在访问将丢失时预测命中,并且在访问可能命中时预测丢失。无论哪种情况,都会发起对 L2 缓存的填充请求,并且当 L2 响应填充请求时更新 utag。

当两个不同的线性地址映射到同一物理地址时,就会发生线性别名。这可能会导致加载和存储别名缓存行的性能下降。对在 L1 DC 中有效但在不同线性别名下的地址的加载将看到 L1 DC 未命中,这需要发出 L2 缓存请求。延迟通常不会大于 L2 缓存命中的延迟。然而,如果多个别名加载或存储同时进行,则它们每个都可能会遇到 L1 DC 缺失,因为它们使用特定的线性地址更新 utag 并删除另一个能够访问高速缓存行的线性地址。

如果两个不同的线性地址没有别名到同一物理地址,那么它们也可能在 utag 中发生冲突(如果它们具有相同的线性哈希)。在给定的 L1 DC 索引 (11:6) 下,任何时候都只能访问具有给定线性哈希的一个缓存行;任何具有匹配线性散列的缓存行在 utag 中都被标记为无效并且不可访问。


  1. utag在两个方向上都有可能出错

第二段这句话的具体场景是什么?什么情况下命中预测为未命中,未命中预测为命中?当CPU从内存访问数据到cache时,会根据utag计算出一条cache way。然后就把它放在这里?即使其他缓存路为空?

  1. 当两个不同的线性地址映射到同一物理地址时,就会发生线性别名。

不同的线性地址如何映射到同一个物理地址?

  1. 然而,如果多个别名加载或存储同时进行,则它们每个都可能会遇到 L1 DC 缺失,因为它们使用特定的线性地址更新 utag 并删除另一个能够访问高速缓存行的线性地址。

这句话是什么意思呢?我的理解是首先根据线性地址(虚拟地址)计算utag来确定使用哪种缓存方式。然后通过物理地址的tag字段来判断是否是缓存命中?utag如何更新?会被记录在缓存中吗?

  1. 任何具有匹配线性散列的缓存行在 utag 中都被标记为无效并且不可访问。这句话是什么意思呢?

AMD如何判断缓存命中或未命中?为什么有些命中会被视为未命中?有人可以解释一下吗?非常感谢!

Had*_*ais 5

\n

L1 数据缓存标签包含一个基于线性地址的微标签 (utag),\n它使用最初用于访问缓存行的线性地址来标记每个缓存行。

\n
\n

L1D 中的每个缓存行都有一个与其关联的 utag。这意味着utag内存结构的组织方式与L1D完全相同(即8路64组),并且条目之间存在一一对应的关系。utag是根据导致该行被填充到L1D中的请求的线性地址来计算的。

\n
\n

负载使用此 utag 来确定使用其线性地址读取高速缓存的哪一路,该线性地址在通过 TLB 确定负载的物理地址之前可用。

\n
\n

负载的线性地址同时发送到路预测器和 TLB(最好使用术语 MMU,因为有多个 TLB)。使用线性地址 (11:6) 的某些位来选择 utag 存储器中的特定组,并且同时读取该组中的所有 8 个 utag。同时,根据加载请求的线性地址计算utag。当这两个操作完成时,给定的 utag 与存储在集合中的所有 utag 进行比较。utag 内存的维护使得每组中最多可以有一个具有相同值的 utag。如果 utag 内存命中,路径预测器会预测目标缓存行位于 L1D 中相应的缓存条目中。到目前为止,还不需要物理地址。

\n
\n

utag 是负载线性地址的哈希值。

\n
\n

在标题为Take A Way: Exploring the Security Implications of AMD\xe2\x80\x99s Cache Way Predictors 的论文中,第 3 节针对多种微架构对哈希函数进行了逆向工程。基本上,位置 27:12 处的线性地址的某些位相互异或以产生 8 位值,即 utag。一个好的哈希函数应该:(1)最小化映射到同一 utag 的线性地址对的数量,(2)最小化 utag 的大小,以及(3)延迟不大于 utag 内存访问延迟。

\n
\n

这种基于线性地址的查找可以在读取缓存数据之前非常准确地预测缓存行的位置。这允许负载仅读取单个缓存方式,而不是\n全部 8 个。这可以节省电量并减少存储体冲突。

\n
\n

除了utag存储器和相关逻辑之外,L1D还包括标签存储器和数据存储器,它们都具有相同的组织。标签存储器存储物理标签(物理地址的第6位到最高位)。数据存储器存储高速缓存行。如果 utag 命中,路预测器仅读取标签存储器和数据存储器中相应路中的一个条目。在现代 x86 处理器上,物理地址的大小超过 35 位,因此物理标签的大小超过 29 位。这比 utag 的大小大 3 倍多。如果没有路预测,在具有多个缓存路的缓存中,必须并行读取和比较多个标签。在8路缓存中,读取和比较1个标签比读取和比较8个标签消耗的能量要少得多。

\n

在每个路都可以单独激活的缓存中,每个缓存条目都有自己的字线,与跨多个缓存路共享的世界线相比,该字线更短。由于信号传播延迟,读取单路所需的时间比读取 8 路所需的时间要少。然而,在并行访问的缓存中,不存在预测延迟,但线性地址转换成为加载延迟的关键路径。通过路径预测,来自预测条目的数据可以推测性地转发到相关微指令。这可以提供显着的加载延迟优势,特别是因为线性地址转换延迟可能由于 MMU 的多级设计而变化,即使在 MMU 命中的典型情况下也是如此。缺点是它引入了可能发生重放的新原因:如果预测错误,可能需要重放数十甚至数百个微指令。我不知道 AMD 是否在验证预测之前实际转发了请求的数据,但即使手册中没有提及,也是有可能的。

\n

减少银行冲突是手册中提到的路径预测的另一个优点。这意味着不同的银行有不同的方式。第 2.6.2.1 节指出,地址的第 5:2 位、访问的大小和高速缓存路号决定了要访问的存储体。这表明有 16*8 = 128 个库,每种方式中每个 4 字节块对应一个库。位5:2从负载的线性地址获得,负载的大小从负载uop获得,路号从路预测器获得。第 2.6.2 节指出,L1D 在同一周期中支持两个 16 字节加载和一个 16 字节存储。这表明每个存储体都有一个 16 字节读写端口。128 个组端口中的每一个都通过互连连接到 L1D 数据存储器的 3 个端口中的每一个。3 个端口之一连接到存储缓冲区,另外两个连接到加载缓冲区,可能具有用于有效处理跨线加载的中间逻辑(单个加载 uop,但两个加载请求的结果被合并)、重叠加载(以避免银行冲突),以及跨越银行边界的负载。

\n

事实上,路预测只需要访问 L1D 的标签存储器和数据存储器中的单个路,从而可以减少或完全消除使标签和数据存储器真正实现多端口化的需要(取决于监听的处理方式)(这是英特尔在 Haswell 中遵循的方法),同时仍然实现了大致相同的吞吐量。但是,当同时访问相同的方式和相同的 5:2 地址位但使用不同的 utag 时,仍然可能会发生存储体冲突。方式预测确实减少了存储体冲突,因为它不需要每次访问读取多个条目(至少在标签存储器中,但也可能在数据存储器中),但它并不能完全消除存储体冲突。

\n

也就是说,标签存储器可能需要真正的多端口来处理填充检查(见下文)、验证检查(见下文)、窥探和非加载访问的“正常路径”检查。我认为只有加载请求才使用预测器的方式。其他类型的请求均正常处理。

\n

高度准确的 L1D 命中/未命中预测还可以带来其他好处。如果预测 L1D 中的负载会丢失,则可以抑制相关微指令的调度程序唤醒信号以避免可能的重播。此外,物理地址一旦可用,就可以在完全解决预测之前提前发送到 L2 缓存。我不知道AMD是否采用了这些优化。

\n
\n

utag 有可能在两个方向上都是错误的:它可以\n预测访问何时会失败,并且\n可以预测访问何时\n可能命中。无论哪种情况,都会发起对 L2 缓存的填充请求,并且当 L2 响应填充请求时更新 utag。

\n
\n

在支持多个线性地址空间或允许同一地址空间中存在同义词的操作系统上,缓存行只能使用物理地址唯一标识。正如前面提到的,当在 utag 内存中查找 utag 时,可能有一次命中,也可能有零次命中。首先考虑命中情况。这种基于地址的线性查找会导致推测命中,但仍需要验证。即使禁用分页,utag 仍然不是完整地址的唯一替代品。一旦 MMU 提供了物理地址,就可以通过将预测路径中的物理标签与访问物理地址中的标签进行比较来验证预测。可能会发生以下情况之一:

\n
    \n
  1. 物理标签匹配并且推测命中被视为真实命中。除了可能触发预取或更新行的替换状态之外,无需执行任何操作。
  2. \n
  3. 物理标记不匹配,并且目标行不存在于同一组的任何其他条目中。请注意,目标行不可能存在于其他集合中,因为所有 L1D 存储器都使用相同的集合索引功能。稍后我将讨论如何处理。
  4. \n
  5. 物理标记不匹配,并且目标行确实存在于同一组的另一个条目中(与不同的 utag 关联)。稍后我将讨论如何处理。
  6. \n
\n

如果在 utag 内存中没有找到匹配的 utag,则不会有可比较的物理标签,因为没有预测方法。可能会发生以下情况之一:

\n
    \n
  1. 目标线实际上并不存在于 L1D 中,因此推测性未命中是真正的未命中。该线路必须从其他地方获取。
  2. \n
  3. 目标行实际上存在于同一集合中,但具有不同的 utag。稍后我将讨论如何处理。
  4. \n
\n

(我在这里做了两个简化。首先,加载请求被假定为可缓存内存。其次,在 L1D 中的推测或真实命中中,数据中没有检测到错误。我正在尝试请重点关注第 2.6.2.2 节。)

\n

仅在情况 3 和 5 中需要访问 L2,而在情况 2 和 4 中则不需要。确定是哪种情况的唯一方法是通过将负载的物理标签与同一组中所有现有线路的物理标签进行比较。这可以在访问 L2 之前或之后完成。无论哪种方式,都必须这样做以避免 L1D 中同一行有多个副本的可能性。在访问 L2 之前进行检查可以改善情况 3 和 5 中的延迟,但会损害情况 2 和 4 中的延迟。访问 L2 之后进行检查可以改善情况 2 和 4 中的延迟,但会损害情况 3 和 5 中的延迟。可以同时执行检查并向 L2 发送请求。但在情况 3 和 5 中,这可能会浪费能量和 L2 带宽。AMD 似乎决定在从 L2(包括 L1 缓存)获取行后进行检查。

\n

当线路从 L2 到达时,L1D 不必等到填充完毕即可响应请求的数据,因此可以容忍较高的填充延迟。现在比较物理标签以确定发生了 4 种情况中的哪一种。在情况 4 中,该行按照替换策略选择的方式填充到数据存储器、标签存储器和 utag 存储器中。在情况 2 中,请求的线路替换了恰好具有相同 utag 的现有线路,并且替换策略不参与选择路径。即使同一组中存在空闲条目,也会发生这种情况,从本质上减少了缓存的有效容量。在情况 5 中,可以简单地覆盖 utag。情况 3 有点复杂,因为它涉及一个具有匹配物理标签的条目和一个具有匹配 utag 的不同条目。其中之一必须失效,另一者必须更换。在这种情况下,也可能存在未使用的空条目。

\n
\n

当两个不同的线性地址映射到同一物理地址时,就会发生线性别名。这可能会导致加载和存储别名缓存行时的性能下降。加载到在 L1 DC 中有效但在不同的线性别名下的地址将出现 L1 DC 未命中,这需要发出 L2 缓存请求。延迟通常不会大于 L2 缓存命中的延迟。但是,如果多个别名加载或存储同时进行,则它们在使用特定线性地址更新 utag 并删除另一个线性地址以使其无法访问缓存行时,可能会遇到 L1 DC 缺失。

\n
\n

这就是情况 5(以及较小程度上的情况 2)的发生方式。线性别名可以发生在相同的线性地址空间内和不同的地址空间之间(上下文切换和超线程效应发挥作用)。

\n
\n

如果两个不同的线性地址没有别名到同一物理地址,那么它们也可能在 utag 中发生冲突(如果它们具有相同的线性哈希)。在给定的 L1 DC 索引 (11:6) 处,任何时候都只能访问一个具有给定线性散列的缓存行;具有匹配线性散列的任何\n缓存行在 utag 中被标记为无效\n并且不可访问。

\n
\n

这就是情况 2 和 3 的发生方式,并且按照前面讨论的方式进行处理。这部分讲述了L1D使用简单集合索引功能;设置编号为位 11:6。

\n

我认为大页使情况 2 和 3 更有可能发生,因为 utag 哈希函数使用的一半以上的位成为页偏移量而不是页号的一部分。多个操作系统进程之间共享的物理内存使情况 5 的可能性更大。

\n