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

Lew*_*sey 5 cache intel-core-i7 cpu-architecture cpu-cache

根据Real World Technologies关于“英特尔的 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 将使用与用于从 L1 指令缓存中检索 16 字节提取块相同的虚拟地址进入 uop 缓存(以便它们可以在每 32B 边际并行探测)

它说这些 uops 可以跨越一组中 8 种方式中的 3 种,但这意味着它们必须具有相同的设置位但不同的标记位才能最终出现在同一组中(意味着它们不会在L1I 缓存中的同一行),这是否意味着 uop 缓存的排列略有不同,行开头的单个虚拟地址和 uop 只是填充到集合中的下一条路和下一条路放。如何确保下一个 32B 指令窗口仍然具有相同的标签和相同的集合位但不同的偏移位(L1I 中 64 B 行的第二半)映射到该集合的第 4 路。

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

任何人都可以澄清 uop 缓存的布局或这种标记实际上是如何工作的吗?

Pet*_*des 4

请注意,AMD Zen 也有 uop 缓存,但对其内部结构知之甚少。因此,您具体询问的是有关 Sandybridge 系列中 Intel 的 uop 缓存的信息。

\n\n

根据 Agner Fog 的测试(https://www.agner.org/optimize/,特别是他的 microarch pdf),它已被虚拟解决(VIVT),节省了 uop-cache 的 iTLB 查找的延迟/功率命中。并且可以将 iTLB 与 L1i 缓存非常紧密地集成在一起,就像 VIPT L1 缓存的正常情况一样。

\n\n

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

\n\n
\n

一旦 32 字节窗口被解码

\n
\n\n

这就是你的思维过程出错的地方。

\n\n

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

\n\n

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

\n\n

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

\n\n

但有趣的是,Agner 说“如果有多个跳转条目,同一段代码可以在 \xce\xbcop 缓存中拥有多个条目

\n\n
\n\n

我对缓存查找机制实际工作原理的最佳猜测:

\n\n

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

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

使用 5 位索引来选择一组。
\n从该集合中获取所有 8 种方式(标签 + 元数据,以及并行数据,因为这是高性能缓存)。

\n\n

并行比较所有 8 种方式:

\n\n
    \n
  • 标签位全部匹配
  • \n
  • offset 在 x86 机器代码的 start+length 范围内,这样可以缓存 uops。(一种方法只能缓存 1 个连续的 x86 机器代码块的微指令)。
  • \n
\n\n

对于给定的指令地址,集合中最多有 1 个路同时满足两个条件。 如果有的话,这就是你的命中,你可以从匹配的一种方式获取微指令。(与常规字节缓存一样,除了您需要检查元数据以选择从哪个微指令开始获取(如果您跳到中间)。)

\n\n

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

\n\n
\n\n

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

\n\n

L1i 获取块/指令长度的预解码是对齐的,但传统解码器中的完整解码适用于从预解码和解码之间的队列中获取的最多 16 个字节的任何对齐方式。循环入口点与某些对齐边界的对齐不像以前那么重要了。

\n\n
\n\n

然后我猜想需要检查提取地址是否与所选方式的指令起始地址之一完全匹配。这没有得到有效支持,因为只有混淆代码才能以两种不同的方式解码相同的字节。

\n\n

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

\n\n

然后,当它从此时开始解码微指令时,它可以开始重新填充微指令缓存。

\n\n

当 3 个路已经满,但是来自 x86 机器代码的相同 32B 块中有更多微指令时,会发生类似的情况。uop 缓存会抛出该块的所有 3 种方式。(我不确定它是否记得下次不要尝试缓存它们,或者它是否只是每次都构建缓存并在达到限制时将其丢弃,例如在具有 20 个单字节指令的循环nop中.)

\n\n

有关此案例的一些详细信息,请参阅涉及 Intel SnB 系列 CPU 上微编码指令的循环的分支对齐。请注意,微编码指令(例如,div自己用完 uop 缓存的整个路),很容易导致填满所有 3 个路并触发 DSB 到 MITE 开关(uop 缓存到传统解码开关可以创建 1前端循环气泡)。

\n\n

该问答有很多关于微指令如何缓存的详细实验和结论。与其说是关于 uop 缓存是如何物理实现的,不如说是关于 uop 缓存是如何物理实现的。这纯粹是我的猜测。

\n\n

另请注意,Skylake 之前的 Intel CPU 只能从 uop 缓存向 IDQ 添加 4 uops,但当 uop 缓存中存在具有 3 或 6 uops 而不是 4 的方式时,不知何故不会出现瓶颈。所以 IDK 如果有\是用于非分支 uop 获取的某种缓冲。这有点神秘。如果从每行 6 个 uop 的整行中获取,您可能会期望 fetch 以 4、2、4、2 模式进行,但我们没有看到像从 uop 缓存运行的循环那样的前端瓶颈使用 2 字节指令,例如xor eax,eax. Intel 表示 uop 缓存每个周期只能从 1 路获取 uop,因此 4-uop 限制可能只是为了添加到 IDQ,实际上并不是为了从 uop 缓存读取到某个合并缓冲区。

\n