在一个指令周期内如何执行微代码?
How are microcodes executed during an instruction cycle?
从开放资源中我可以得出结论,微码大约是可以被CPU直接执行的东西,负责实现指令代码。 Wikipedia 表示指令代码的每次执行都将经历一个获取-解码-执行指令周期。但是,我找不到任何参考资料来解释在这个三阶段循环中微码执行是如何完成的。所以我的问题是,微码执行和指令周期有什么关系?微码在指令执行的取指、解码和执行阶段如何工作?
另外这个 说在现代英特尔 CPU 中,即使是最简单的指令,如 DIV
和 MOV
也会在执行前编译成微码,所以它会最好有人能用 CPU 中的例子来解释它,如果那确实是真的。
div
并不简单,它是最难计算的整数运算之一!它在 Intel CPUs 上进行了微编码,这与 mov
、add
/sub
甚至 imul
不同,后者在现代 Intel 上都是 single-uop。有关说明表和微架构指南,请参阅 https://agner.org/optimize/。 (有趣的事实:AMD Ryzen 没有微码 div
;它只有 2 微码,因为它必须写入 2 个输出寄存器。打桩机和后来的 32 位和 64 位除法 2 微码。)
所有指令解码为 1 或更多 uop(大多数程序中的大多数指令在当前 CPUs 上为 1 uop)。在 Intel CPU 上解码为 4 或更少微指令的指令被描述为 "not microcoded",因为它们不使用 many-uop 指令的特殊 MSROM 机制。
没有 CPU 将 x86 指令解码为 uops 使用简单的三相 fetch/decode/exec 循环 ,所以你问题的那部分前提没有意义。同样,请参阅 Agner Fog 的微架构指南。
您确定要询问有关现代英特尔 CPU 的信息吗?一些较旧的 CPUs 是内部微编码的,尤其是 non-pipelined CPUs,其中执行不同指令的过程可以以不同的顺序激活不同的内部逻辑块。 控制它的逻辑也称为微码,但它是一种不同于流水线上下文中术语现代含义的微码 out-of-order CPU.
如果这就是您要查找的内容,请参阅 retrocomputing.SE 上的 How was microcode implemented in retro processors? 以获得 non-pipelined CPU 之类的 6502和 Z80,其中记录了一些微码内部时序周期。
微代码指令如何在现代英特尔 CPUs 上执行?
当微编码"indirect uop"到达Sandybridge-familyCPU中IDQ的头部时,它接管了issue/rename 阶段并从 microcode-sequencer MS-ROM 提供微指令,直到指令发出所有微指令,然后 front-end 可以继续向 out-of-order [=157= 发出其他微指令].
IDQ 是为 issue/rename 级提供指令解码队列(它从 front-end 发送微指令到 out-of-order back-end)。它缓冲来自 uop 缓存 + 传统解码器的 uops,以吸收气泡和爆裂。它是 David Kanter's Haswell block diagram 中的 56 uop 队列。 (但这表明微码仅在之前队列被读取,这与英特尔对某些性能事件的描述不匹配1,或者必须发生的事情对于 运行 data-dependent 微指令数的微码指令。
(这可能不是 100% 准确,但至少可以作为大多数性能影响的心智模型2。对于我们目前观察到的性能影响,可能还有其他解释。)
这只发生在需要超过 4 微指令的指令上;需要 4 个或更少的解码以在普通解码器中分离 uops 并且可以正常发出的指令。例如xchg eax, ecx
在现代英特尔上是 3 微指令: 详细介绍了我们可以弄清楚这些微指令实际上是什么。
微码指令的特殊 "indirect" uop 在 decoded-uop 高速缓存 DSB (potentially causing code-alignment performance issue) 中占用整行。我不确定他们是否只在从 uop 缓存 and/or 遗留解码器 IDQ 提供问题阶段的队列中获取 1 个条目。反正我编了个词"indirect uop"来形容。它实际上更像是 not-yet-decoded 指令或指向 MS-ROM 的指针。 (可能一些微码指令可能是一对 "normal" 微指令和一个微码指针;这可以解释它占用了整条 uop-cache 行。)
我很确定它们在到达队列头部之前不会完全扩展,因为一些微编码指令的微指令数是可变的,具体取决于寄存器中的数据。值得注意的是 rep movs
,它基本上实现了 memcpy
。实际上这很棘手;根据对齐方式和大小使用不同的策略,rep movs
实际上需要做一些条件分支。但它跳转到不同的 MS-ROM 位置,而不是不同的 x86 machine-code 位置(RIP 值)。参见 。
Intel's fast-strings patent 也阐明了 P6 中的原始实现:第一个 n
复制迭代在 back-end 中断言;并给 back-end 时间将 ECX 的值发送到 MS。由此,如果需要更多,微码定序器可以发送正确数量的复制 uops,而无需在 back-end 中进行分支。也许处理 nearly-overlapping src 和 dst 或其他特殊情况的机制毕竟不是基于分支的,但 Andy Glew 确实提到缺乏微代码分支预测是实现的一个问题。所以我们知道他们很特别。那是在 P6 时代; rep movsb
现在更复杂了。
根据指令,它可能会或可能不会耗尽 out-of-order 后端的保留站,即调度程序,同时整理出要做什么。 rep movs
在 Skylake 上对大于 96 字节的副本执行此操作,不幸的是tely(根据我对性能计数器的测试,将 rep movs
放在 imul
的独立链之间)。这可能是由于错误预测的微代码分支造成的,这与常规分支不同。也许 branch-miss fast-recovery 对它们不起作用,所以在它们退休之前不会检测/处理它们? (有关更多信息,请参阅微码分支问答)。
rep movs
与mov
有很大的不同。正常的 mov
像 mov eax, [rdi + rcx*4]
是一个单一的 uop,即使有一个复杂的寻址模式。一个 mov
store 是 1 micro-fused uop,包括一个 store-address 和 store-data uop,它们可以按任何顺序执行,将数据和物理地址写入存储缓冲区,因此在指令从 out-of-order back-end 退出并变为 non-speculative 后,store 可以提交到 L1d。 rep movs
的微代码将包含许多加载和存储微指令。
脚注 1:
我们知道 Skylake 上有像 idq.ms_dsb_cycles
这样的性能事件:
[Cycles when uops initiated by Decode Stream Buffer (DSB) are being delivered to Instruction Decode Queue (IDQ) while Microcode Sequenser[sic] (MS) is busy]
如果微码只是输入 IDQ 前端的第三种可能的 uops 来源,那将毫无意义。但是后来有一个事件的描述听起来像这样:
idq.ms_switches
[Number of switches from DSB (Decode Stream Buffer) or MITE (legacy
decode pipeline) to the Microcode Sequencer]
我认为这实际上意味着当issue/rename阶段切换到从微码定序器而不是IDQ(它保存来自DSB的微指令and/or螨)。并不是说 IDQ 切换其传入 uops 的来源。
脚注 2:
为了测试这个理论,我们可以构造一个测试用例,在微代码指令之后有很多 easily-predicted 跳转到冷 i-cache 行,看看 front-end 进入多远在执行大 rep scasb
.
期间,跟随缓存未命中并将 uops 排队到 IDQ 和其他内部缓冲区中
SCASB 没有 fast-strings 支持,因此它非常慢并且每个周期不会触及大量内存。我们希望它在 L1d 中命中,因此时间是高度可预测的。可能几 4k 页就足以让 front-end 跟随大量 i-cache 未命中。我们甚至可以将连续的虚拟页面映射到相同的物理页面(例如,从文件上的 user-space 和 mmap
)
如果微码指令后面的 IDQ space 可以在执行时用后面的指令填充,这就为 front-end 留下了更多空间,可以从前面的更多 i-cache 行中获取当他们需要的时候。然后我们可以希望检测到总周期and/or其他性能计数器的差异,运行宁rep scasb
加上一系列跳跃。在每次测试之前,在包含跳转指令的行上使用clflushopt
。
为了以这种方式测试 rep movs
,我们也许可以使用虚拟内存来获得映射到同一物理页面的连续页面,再次为我们提供加载 + 存储的 L1d 命中,但 dTLB 延迟会很困难控制。或者甚至在 no-fill 模式下使用 CPU 启动,但这很难使用,需要自定义 "kernel" 才能将结果放在可见的地方。
我非常有信心我们会发现 uops 进入 IDQ 而微代码指令已经接管了 front-end(如果它还没有满的话)。有一个性能事件
idq.ms_uops
[Uops delivered to Instruction Decode Queue (IDQ) while Microcode
Sequenser (MS) is busy]
和其他 2 个类似的事件只计算来自 MITE(传统解码)的 uops 或来自 DSB(uop 缓存)的 uops。英特尔对这些事件的描述与我对微码指令 ("indirect uop") 如何接管问题阶段以从微码定序器/ROM 读取微指令而其余 front-end 继续执行其操作的描述相一致将 uops 传送到 IDQ 的另一端,直到它填满。
从开放资源中我可以得出结论,微码大约是可以被CPU直接执行的东西,负责实现指令代码。 Wikipedia 表示指令代码的每次执行都将经历一个获取-解码-执行指令周期。但是,我找不到任何参考资料来解释在这个三阶段循环中微码执行是如何完成的。所以我的问题是,微码执行和指令周期有什么关系?微码在指令执行的取指、解码和执行阶段如何工作?
另外这个 DIV
和 MOV
也会在执行前编译成微码,所以它会最好有人能用 CPU 中的例子来解释它,如果那确实是真的。
div
并不简单,它是最难计算的整数运算之一!它在 Intel CPUs 上进行了微编码,这与 mov
、add
/sub
甚至 imul
不同,后者在现代 Intel 上都是 single-uop。有关说明表和微架构指南,请参阅 https://agner.org/optimize/。 (有趣的事实:AMD Ryzen 没有微码 div
;它只有 2 微码,因为它必须写入 2 个输出寄存器。打桩机和后来的 32 位和 64 位除法 2 微码。)
所有指令解码为 1 或更多 uop(大多数程序中的大多数指令在当前 CPUs 上为 1 uop)。在 Intel CPU 上解码为 4 或更少微指令的指令被描述为 "not microcoded",因为它们不使用 many-uop 指令的特殊 MSROM 机制。
没有 CPU 将 x86 指令解码为 uops 使用简单的三相 fetch/decode/exec 循环 ,所以你问题的那部分前提没有意义。同样,请参阅 Agner Fog 的微架构指南。
您确定要询问有关现代英特尔 CPU 的信息吗?一些较旧的 CPUs 是内部微编码的,尤其是 non-pipelined CPUs,其中执行不同指令的过程可以以不同的顺序激活不同的内部逻辑块。 控制它的逻辑也称为微码,但它是一种不同于流水线上下文中术语现代含义的微码 out-of-order CPU.
如果这就是您要查找的内容,请参阅 retrocomputing.SE 上的 How was microcode implemented in retro processors? 以获得 non-pipelined CPU 之类的 6502和 Z80,其中记录了一些微码内部时序周期。
微代码指令如何在现代英特尔 CPUs 上执行?
当微编码"indirect uop"到达Sandybridge-familyCPU中IDQ的头部时,它接管了issue/rename 阶段并从 microcode-sequencer MS-ROM 提供微指令,直到指令发出所有微指令,然后 front-end 可以继续向 out-of-order [=157= 发出其他微指令].
IDQ 是为 issue/rename 级提供指令解码队列(它从 front-end 发送微指令到 out-of-order back-end)。它缓冲来自 uop 缓存 + 传统解码器的 uops,以吸收气泡和爆裂。它是 David Kanter's Haswell block diagram 中的 56 uop 队列。 (但这表明微码仅在之前队列被读取,这与英特尔对某些性能事件的描述不匹配1,或者必须发生的事情对于 运行 data-dependent 微指令数的微码指令。
(这可能不是 100% 准确,但至少可以作为大多数性能影响的心智模型2。对于我们目前观察到的性能影响,可能还有其他解释。)
这只发生在需要超过 4 微指令的指令上;需要 4 个或更少的解码以在普通解码器中分离 uops 并且可以正常发出的指令。例如xchg eax, ecx
在现代英特尔上是 3 微指令:
微码指令的特殊 "indirect" uop 在 decoded-uop 高速缓存 DSB (potentially causing code-alignment performance issue) 中占用整行。我不确定他们是否只在从 uop 缓存 and/or 遗留解码器 IDQ 提供问题阶段的队列中获取 1 个条目。反正我编了个词"indirect uop"来形容。它实际上更像是 not-yet-decoded 指令或指向 MS-ROM 的指针。 (可能一些微码指令可能是一对 "normal" 微指令和一个微码指针;这可以解释它占用了整条 uop-cache 行。)
我很确定它们在到达队列头部之前不会完全扩展,因为一些微编码指令的微指令数是可变的,具体取决于寄存器中的数据。值得注意的是 rep movs
,它基本上实现了 memcpy
。实际上这很棘手;根据对齐方式和大小使用不同的策略,rep movs
实际上需要做一些条件分支。但它跳转到不同的 MS-ROM 位置,而不是不同的 x86 machine-code 位置(RIP 值)。参见
Intel's fast-strings patent 也阐明了 P6 中的原始实现:第一个 n
复制迭代在 back-end 中断言;并给 back-end 时间将 ECX 的值发送到 MS。由此,如果需要更多,微码定序器可以发送正确数量的复制 uops,而无需在 back-end 中进行分支。也许处理 nearly-overlapping src 和 dst 或其他特殊情况的机制毕竟不是基于分支的,但 Andy Glew 确实提到缺乏微代码分支预测是实现的一个问题。所以我们知道他们很特别。那是在 P6 时代; rep movsb
现在更复杂了。
根据指令,它可能会或可能不会耗尽 out-of-order 后端的保留站,即调度程序,同时整理出要做什么。 rep movs
在 Skylake 上对大于 96 字节的副本执行此操作,不幸的是tely(根据我对性能计数器的测试,将 rep movs
放在 imul
的独立链之间)。这可能是由于错误预测的微代码分支造成的,这与常规分支不同。也许 branch-miss fast-recovery 对它们不起作用,所以在它们退休之前不会检测/处理它们? (有关更多信息,请参阅微码分支问答)。
rep movs
与mov
有很大的不同。正常的 mov
像 mov eax, [rdi + rcx*4]
是一个单一的 uop,即使有一个复杂的寻址模式。一个 mov
store 是 1 micro-fused uop,包括一个 store-address 和 store-data uop,它们可以按任何顺序执行,将数据和物理地址写入存储缓冲区,因此在指令从 out-of-order back-end 退出并变为 non-speculative 后,store 可以提交到 L1d。 rep movs
的微代码将包含许多加载和存储微指令。
脚注 1:
我们知道 Skylake 上有像 idq.ms_dsb_cycles
这样的性能事件:
[Cycles when uops initiated by Decode Stream Buffer (DSB) are being delivered to Instruction Decode Queue (IDQ) while Microcode Sequenser[sic] (MS) is busy]
如果微码只是输入 IDQ 前端的第三种可能的 uops 来源,那将毫无意义。但是后来有一个事件的描述听起来像这样:
idq.ms_switches
[Number of switches from DSB (Decode Stream Buffer) or MITE (legacy decode pipeline) to the Microcode Sequencer]
我认为这实际上意味着当issue/rename阶段切换到从微码定序器而不是IDQ(它保存来自DSB的微指令and/or螨)。并不是说 IDQ 切换其传入 uops 的来源。
脚注 2:
为了测试这个理论,我们可以构造一个测试用例,在微代码指令之后有很多 easily-predicted 跳转到冷 i-cache 行,看看 front-end 进入多远在执行大 rep scasb
.
SCASB 没有 fast-strings 支持,因此它非常慢并且每个周期不会触及大量内存。我们希望它在 L1d 中命中,因此时间是高度可预测的。可能几 4k 页就足以让 front-end 跟随大量 i-cache 未命中。我们甚至可以将连续的虚拟页面映射到相同的物理页面(例如,从文件上的 user-space 和 mmap
)
如果微码指令后面的 IDQ space 可以在执行时用后面的指令填充,这就为 front-end 留下了更多空间,可以从前面的更多 i-cache 行中获取当他们需要的时候。然后我们可以希望检测到总周期and/or其他性能计数器的差异,运行宁rep scasb
加上一系列跳跃。在每次测试之前,在包含跳转指令的行上使用clflushopt
。
为了以这种方式测试 rep movs
,我们也许可以使用虚拟内存来获得映射到同一物理页面的连续页面,再次为我们提供加载 + 存储的 L1d 命中,但 dTLB 延迟会很困难控制。或者甚至在 no-fill 模式下使用 CPU 启动,但这很难使用,需要自定义 "kernel" 才能将结果放在可见的地方。
我非常有信心我们会发现 uops 进入 IDQ 而微代码指令已经接管了 front-end(如果它还没有满的话)。有一个性能事件
idq.ms_uops
[Uops delivered to Instruction Decode Queue (IDQ) while Microcode Sequenser (MS) is busy]
和其他 2 个类似的事件只计算来自 MITE(传统解码)的 uops 或来自 DSB(uop 缓存)的 uops。英特尔对这些事件的描述与我对微码指令 ("indirect uop") 如何接管问题阶段以从微码定序器/ROM 读取微指令而其余 front-end 继续执行其操作的描述相一致将 uops 传送到 IDQ 的另一端,直到它填满。