微操作缓存是如何标记的?

微操作缓存是如何标记的?

根据现实世界的技术' 文章“英特尔的 Sandy Bridge 微架构”:

“Sandy Bridge 的 uop 缓存分为 32 组和 8 路,每行 6 uop,总共 1.5K uop 容量。uop 缓存严格包含在 L1 指令缓存中。每行还保存元数据,包括行中有效 uop 的数量以及与 uop 缓存行相对应的 x86 指令的长度。映射到 uop 缓存中的每个 32B 窗口可以跨越一组 8 路中的 3 路,最多 18 uop - 大约 1.8B/uop。如果 32B 窗口有超过 18 uop,则它无法放入 uop 缓存中,必须使用传统的前端。微码指令不保存在 uop 缓存中,而是由指向微码 ROM 的指针和可选的前几个 uop 表示。”

‘每个 32B 窗口(来自指令缓存)被映射到 uop 缓存中,可以跨越一组 8 种方式中的 3 种’

因此假设我们有一个 32B 指令窗口,它是 L1 指令缓存行的一半,在该行上,只有偏移位会不同,但标签和设置位对于该行的所有字节都是相同的。

一旦解码了 32 字节窗口,uop 就会进入 uop 缓存,其虚拟地址与从 L1 指令缓存中检索 16 字节获取块时使用的虚拟地址相同(这样就可以在每个 32B 边距处并行探测它们)

它说这些 uop 可以跨越一组 8 条路中的 3 条,但这意味着它们必须具有相同的设置位但不同的标记位才能最终进入同一组(这意味着它们不会位于 L1I 缓存中的同一行),这是否意味着 uop 缓存的排列略有不同,一行开头有一个虚拟地址,而 uop 刚好填充到集合中的下一条路和集合中的下一条路中。如何确保下一个 32B 指令窗口仍具有相同的标记和相同的设置位但不同的偏移位(L1I 中 64 B 行的后半部分)映射到该集合的第 4 条路。

假设:uop 缓存路标有虚拟索引物理标记,下一条路标有无标记,第三条路标有无标记,第四条路标有虚拟索引/物理标记,不同之处在于偏移量从 0 变为 32,因此本质上,可以使用不同的偏移位来选择一种方式,而不是像 L1I 缓存标记的方式那样:使用偏移位作为缓存行的偏移量。

有人能阐明 uop 缓存的布局或这种标记实际上是如何工作的吗?

答案1

请注意,AMD Zen 也有一个 uop 缓存,但对其内部结构知之甚少。所以你问的是 Sandybridge 系列中英特尔的 uop 缓存。

根据 Agner Fog 的测试(https://www.agner.org/optimize/,特别是他的微架构 pdf),它是虚拟寻址的(VIVT),节省了 uop 缓存命中的 iTLB 查找延迟/功耗。并且仍然可以非常紧密地将 iTLB 与 L1i 缓存集成在一起,就像 VIPT L1 缓存一样。

(另相关:英特尔酷睿 i7 处理器使用哪种缓存映射技术?有关该缓存和其他缓存的摘要,以及https://stackoverflow.com/tags/x86/info以获取更多性能/uarch 链接。)

一旦 32 字节窗口被解码

这就是你的思维过程出了问题。

uop 缓存仅缓存沿(推测)执行路径解码的 uop。只有知道正确的起点,才能正确解码 x86 指令。无条件指令后的字节jmp可能根本不是指令的开头。

另外,你也不想在函数之间用许多单字节填充指令来污染 uop 缓存(例如 0x90 NOP 或0xcc int3MSVC 使用)。或者一般来说,使用在执行分支后正常执行期间未达到的“冷”指令。uop-cache“行”/路以无条件跳转或 提前结束call

传统解码器要么解码 CPU 期望实际执行的指令(将它们送入 uop 缓存以供稍后重用,并直接送入 IDQ 以供立即使用),或者电源已关闭。与 P4 不同,传统解码器并不弱;它们与 Core2 / Nehalem 中的解码器类似,因此从 L1i 执行通常没问题,除非是平均指令大小较大的高吞吐量代码。它们不需要提前尝试“构建跟踪”。(uop 缓存是不是无论如何,跟踪缓存;它不跟踪跳转。但无论如何,它不会尝试填充 uop 缓存中的所有 32 个指令字节可以将被立即缓存。

但有趣的是,阿格纳说“如果同一段代码具有多个跳转条目,那么它可以在 μop 缓存中有多个条目


我最好的猜测缓存查找机制实际上是如何工作的:

给定一个 64 位虚拟地址来获取代码:

  • 低 5 位是相对于 32 字节边界的偏移量。
  • 接下来的 5 位是索引。不是 64 字节 L1i 行的 6 位;从 uop 缓存中获取并不直接关心这一点。
  • 较高位(最多到第 48 位)是标签。

使用 5 位索引选择一个集合。
从该集合中获取所有 8 条路径(标签 + 元数据,以及并行数据,因为这是一个高性能缓存)。

对全部 8 种方式进行并行比较:

  • 标记位全部匹配
  • 偏移量在 x86 机器代码的起始+长度范围内,这种方式可以缓存 uops。(一种方法只能缓存 1 个连续的 x86 机器代码块的 uops)。

对于给定的指令地址,集合中最多有 1 种方式会同时满足两个条件。 如果有,则表示命中,您可以从匹配的那条路径中获取微指令。(与常规字节缓存类似,但如果您跳转到一条路径的中间,则需要检查元数据以选择从哪个微指令开始获取。)

这是基于 uop 缓存如何执行以及何时抛出方法的猜测。但它可能有助于您获得一个有用的心理模型。


请注意地址没有需要 16 字节对齐。它需要有效地支持未对齐的分支目标,以及指令边界与 32 字节边界不一致的直线代码。(据我所知,跨越 32 字节边界的指令以 uop 缓存方式缓存在指令的起始地址中,即使它在跨越 64 字节边界的下一个 L1i 缓存行中结束。)

L1i 提取块/预解码的指令长度是对齐的,但旧解码器中的完整解码最多可处理 16 个字节的任何对齐,这些对齐取自预解码和解码之间的队列。循环入口点与某些对齐边界的对齐不像以前那么重要了。


然后我猜想会有一个检查,即获取地址是否与所选方式中的某个指令起始地址完全匹配。这无法有效支持,因为只有混淆的代码才能以两种不同的方式解码相同的字节。

uop 缓存无法同时缓存两种方式,因此在检测到这种情况时,CPU 必须回退到传统的解码器并丢弃此 32B 块的 uop 缓存方式(它已经通过标签比较器检测到)。

然后,它可以从此时点开始重新填充 uop 缓存,因为它可以解码 uop。

当 3 条路径已满,但来自同一 32B x86 机器代码块的微指令更多时,也会发生类似的事情。微指令缓存会丢弃该块的所有 3 条路径。(我不确定它是否记得不要尝试缓存它们以备下次使用,或者它是否每次都构建缓存并在达到限制时将其丢弃,nop例如在包含 20 条单字节指令的循环中。)

英特尔 SnB 系列 CPU 上涉及微编码指令的循环的分支对齐有关此案的一些详细信息。请注意,微编码指令div本身就会占用 uop 缓存的整个路径,并且很容易导致填满所有 3 个路径并触发 DSB 到 MITE 切换(uop 缓存到传统解码切换会在前端产生 1 个周期的泡沫)。

该问答包含大量关于 uop 如何缓存的详细实验和结论。关于 uop 缓存的物理实现方式,我并没有做太多介绍;这纯粹是我个人的猜测。

还要注意,Skylake 之前的英特尔 CPU 只能从 uop 缓存中向 IDQ 添加 4 个 uop,但当 uop 缓存中有 3 或 6 个 uop 而不是 4 个 uop 时,不会出现瓶颈。所以我不知道是否存在某种用于非分支 uop 提取的缓冲。这有点神秘。如果从每行 6 个 uop 的完整行中获取,您可能希望提取以 4、2、4、2 的模式进行,但是对于从 uop 缓存中运行的带有 2 字节指令的循环,我们没有看到像这样的前端瓶颈xor eax,eax。英特尔已经声明 uop 缓存每个周期只能从 1 个路径获取 uop,所以也许 4-uop 的限制只是为了添加到 IDQ,而不是实际上为了从 uop 缓存读取到某个合并缓冲区中。

相关内容