现代 x86 处理器中的指令融合是什么?

2023-12-09

据我了解,指令融合有两种类型:

  1. 微操作融合
  2. 宏观操作融合

微操作是那些可以在1个时钟周期内执行的操作。如果将多个微操作融合起来,我们就得到了一条“指令”。

如果融合多条指令,我们就得到一个宏操作。

如果将多个宏操作融合起来,我们就得到宏操作融合。

我对么?


不,融合与一条复杂指令(例如cpuid or lock add [mem], eax) 可以解码为多个微指令。大多数指令解码为单个 uop,因此这是现代 x86 CPU 中的正常情况。

后端必须跟踪与指令相关的所有微指令,无论是否存在任何微融合或宏融合。当单个指令的所有微指令已从 ROB 中退出时,该指令已退出。 (中断只能在指令边界处进行,因此如果有一个待处理,则退出必须为此找到一个指令边界,而不是在多微指令指令的中间。否则,可以在不考虑指令边界的情况下填充退出槽,例如发出插槽。)


指令之间的宏融合

Macro-fusion decodes cmp/jcc or test/jcc into a single compare-and-branch uop. (Intel and AMD CPUs). The rest of the pipeline sees it purely as a single uop1 (except performance counters still count it as 2 instructions). This saves uop cache space, and bandwidth everywhere including decode. In some code, compare-and-branch makes up a significant fraction of the total instruction mix, like maybe 25%, so choosing to look for this fusion rather than other possible fusions like mov dst,src1 / or dst,src2 makes sense.

Sandybridge-family 还可以将其他一些 ALU 指令与条件分支进行宏融合,例如add/sub or inc/dec+ JCC 有一些条件。 (x86_64 - 汇编 - 循环条件和乱序)

Ice Lake2 changed to doing macro-fusion right after legacy decode, so pre-decode only has to steer 1 x86 instruction to each of the four decoders.


微融合 - 1 条指令内

微融合将来自同一指令的 2 个微指令存储在一起,因此它们仅占用管道的融合域部分中的 1 个“槽”。但他们仍然需要分别调度到不同的执行单元。在Intel Sandybridge系列中,RS(保留站又名调度程序)位于未融合域中,因此它们甚至单独存储在调度程序中。 (参见我的回答中的脚注2了解 lfence 对具有两个长依赖链的循环的影响,以增加长度)

P6家族有一个融合域RS以及ROB,因此微融合有助于增加那里乱序窗口的有效大小。但据报道,SnB 系列简化了 uop 格式,使其更加紧凑,允许更大的 RS 尺寸,这始终很有用,而不仅仅是针对微融合指令。

在某些情况下,Sandybridge 系列将“取消层压”索引寻址模式,在乱序后端将其发出/重命名为 ROB 之前,将它们拆分回各自插槽中的 2 个单独的微指令,这样您就失去了前端-结束问题/重命名微融合的吞吐量优势。看微融合和寻址模式

See also

  • 法纳尔的文章“x86 CPU内部将指令解码为RISC形式”的传说融合域微指令的详细分解sub reg, 1 / jnz包含内存目标的循环add [edx], eax和指针增量,适用于 P6、Pentium-M、Core 2、Sandybridge,以及 Netburst (P4) 和 Bulldozer/Zen。 (忽略调度程序和执行单元中的未融合域微指令计数)。还对微观和宏观融合进行了描述,以防我的措辞不容易理解。
  • https://agner.org/optimize/尤其是他的微架构指南具有逆向工程细节。
  • Intel's 优化手册其中有一些章节涉及微架构细节。
  • https://en.wikichip.org/wiki/macro-operation_fusion(包括 RISC-V 和 ARM 等非 x86。)
  • https://www.corsix.org/content/x86-macro-op-fusion-notes哪些指令+JCC的组合可以熔断。显然,Zen3+ 可以将任何 JCC 与 OR、XOR 和 AND 以及自 Broadwell 以来的 CMP 或 TESTS 进行宏融合。冰湖已移除dec/jcc fusion!
  • https://easyperf.net/blog/2018/02/23/MacroFusion-in-Intel-CPU

两者可以同时发生

    cmp   [rdi], eax
    jnz   .target

在 i7-6700k Skylake 上进行测试,可能适用于大多数早期和后期的 Sandybridge 系列 CPU,尤其是在 Ice Lake 之前。

cmp/jcc 可以宏融合到单个 cmp-and-branch ALU uop 中,并且来自的负载[rdi]可以与该 uop 进行微融合。

微熔断失败cmp不阻止宏融合。

这里的限制是:RIP-relative+immediate永远不能微熔丝,所以cmp dword [static_data], 1 / jnz可以宏熔断但不能微熔断。

A cmp/jcc在 SnB 系列上(例如cmp [rdi+rax], edx / jnz)将在解码器中进行宏观和微观融合,但微观融合将在发布阶段之前取消层压。 (因此,融合域和非融合域中总共有 2 个 uop:使用索引寻址模式加载,并且 ALUcmp/jnz)。您可以通过放置一个性能计数器来验证这一点mov ecx, 1在 CMP 和 JCC 之间对比之后,并注意uops_issued.any:u and uops_executed.thread每次循环迭代两者都会增加 1,因为我们击败了宏融合。微融合的表现也一样。

在天湖上,cmp dword [rdi], 0/jnz无法宏熔断。 (仅限微型保险丝)。我用包含一些虚拟的循环进行了测试mov ecx,1指示。重新排序其中之一mov指令拆分了cmp/jcc没有更改融合域或非融合域微指令的性能计数器。

But cmp [rdi],eax/jnz does宏保险丝和微保险丝。重新排序,这样mov ecx,1指令将 CMP 与 JNZ 分开does更改性能计数器(证明宏融合),并且每次迭代 uops_executed 比 uops_issued 高 1(证明微融合)。

cmp [rdi+rax], eax/jne仅宏熔断器;不是微的。 (事实上​​,解码中的微熔丝,但由于索引寻址模式,在发布之前未层压,并且它不是像 RMW 寄存器目标那样sub eax, [rdi+rax]可以保持索引寻址模式微融合。那sub采用索引寻址模式doesSKL 上的宏和微熔丝,大概还有 Haswell)。

(The cmp dword [rdi],0 does micro-保险丝,不过:uops_issued.any:u低于 1uops_executed.thread,并且循环不包含nop或其他“消除”指令,或任何其他可以微熔丝的内存指令)。

一些编译器(包括 GCC IIRC)更喜欢使用单独的加载指令,然后在寄存器上进行比较+分支。 TODO:检查 gcc 和 clang 的选择对于立即数与寄存器是否是最佳的。


微操作是那些可以在1个时钟周期内执行的操作。

不完全是。它们在管道中占用 1 个“槽”,或者在乱序后端跟踪它们的 ROB 和 RS 中占用 1 个“槽”。

是的,将微指令分派到执行端口会在 1 个时钟周期内发生,并且简单的微指令(例如整数加法)可以在同一周期内完成执行。自 Haswell 以来,这种情况最多可同时发生 8 个 uops,但在 Sunny Cove 上增加到 10 个。实际执行可能需要超过1个时钟周期(占用执行单元较长时间,例如FP除法)。

我认为除法器是现代主流英特尔上唯一没有完全流水线的执行单元,但 Knight's Landing 有一些非完全流水线的 SIMD 洗牌,它们是单微操作,但(倒数)吞吐量为 2 个周期。)。


脚注 1 - 宏融合的 uop 是否需要拆分?

If cmp [rdi], eax / jne内存操作数上的错误,即#PF页面错误异常,异常返回地址指向页面的开头cmp,这样它就可以在页面中的OS页面之后重新运行。无论我们是否有核聚变,这都有效,这并不奇怪。

或者如果分支目标地址是未映射的页面,则#PF会发生异常after该分支已通过更新的 RIP 获取代码执行。

但是如果分支目标地址是非规范的,从架构上来说jcc本身应该#GP过错。例如如果 RIP 接近规范范围的顶部,并且 rel32=+almost2GiB。 (x86-64 的设计使得 RIP 值在内部实际上可以是 48 位或 57 位,永远不需要保存非规范地址,因为尝试设置它时会发生错误,而不是等到从非规范地址获取代码为止- 规范地址。)

如果 CPU 处理该问题时出现异常jcc,不是cmp,然后可以推迟对其进行排序,直到实际检测到异常为止。也许有微代码辅助,或者一些特殊情况的硬件。

另外,TF=1 的单步执行应该在cmp.

至于 cmp/jcc uop 在正常情况下如何通过管道,它的工作方式与一条长单 uop 指令完全相同,两者都设置标志and有条件分支。

令人惊讶的是,loop指令(如dec rcx/jnz但没有设置标志)是notIntel CPU 上的单个 uop。为什么循环指令很慢?英特尔就不能有效地实施它吗?.


脚注2:冰湖变化

阿格纳·福格发现宏观融合发生after传统解码器。 (微融合当然仍然在解码器中,所以指令如下add eax, [rdi]仍然可以在“简单”解码器中解码。)

希望这里的好处是,如果最后一条指令可能是宏熔断指令,则不会提前结束解码组,这是早期 CPU 所做的 IIRC 操作。 (对于大的展开块,传统解码吞吐量较低sub指令对比or不涉及 JCC 时的说明。早期的 CPU 无法进行宏熔断or与任何东西。 这仅影响旧解码,而不影响 uop 缓存。)

Wikichip错误地报告 ICL 每个时钟周期只能进行一次宏融合,但测试两个可熔断对可以在同一时钟周期内解码吗?确认 Rocket Lake(相同的 uarch 向后移植到 14nm)仍然可以像 Haswell 和 Skylake 一样执行 2/clock。

一个来源报道称 Ice Lake 无法进行宏融合inc or dec/jcc(或任何带有内存操作数的指令),但 Agner Fog 的表不同意。uiCA shows dec/jnz在循环宏融合的底部,以及他们的论文显示其预测与真实 CPU(包括 ICL)上的测试非常吻合。但如果他们使用最新的 GCC 进行编译,他们可能不会测试任何dec/jcc loops, sub/jcc。 Agner 的 ICL 融合表不是早期 SnB 的复制/粘贴;表明inc/dec可以在相同情况下熔断add/sub现在(令人惊讶的是包括jc/ja现在,但是dec不修改CF。)如果有人可以测试这个来验证,那就太好了。

update: 诺亚的考验在老虎湖表演中dec/jnz在循环的底部可以进行宏熔断。然后dec/jc似乎没有宏熔断。

微码版本:0x42. decl; jnz仍然循环宏熔丝(niters = nissued_uops = nexecuted_uops = cycles = {expected_ports}).

无法获取decl; jc到宏保险丝。为了decl; jc我设置了两个循环:subl $1, %ecx; decl %eax; jc loop(其中 ecx 是循环计数器)。niters * 3发出/执行的 uops。
还尝试了仅取消设置进位标志和decl %eax; jc done; jnz loop,也是 3 * nitters uops。

Ice Lake 的行为很可能与 Tiger Lake 相同,因为它没有进行重大的微架构更改。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

现代 x86 处理器中的指令融合是什么? 的相关文章

  • 在 Intel x86 架构上使用非 AVX 指令移动 xmm 整数寄存器值

    我有以下问题 需要使用 AVX2 以外的任何工具来解决 我有 3 个值存储在 m128i 变量中 不需要第四个值 需要将这些值移动 4 3 5 我需要两个功能 一个用于按这些值进行右逻辑移位 另一个用于左逻辑移位 有谁知道使用 SSE AV
  • Android NDK 代码中的 SIGILL

    我在市场上有一个 NDK 应用程序 并获得了有关以下内容的本机崩溃报告 SIGILL信号 我使用 Google Breakpad 生成本机崩溃报告 以下是详细信息 我的应用程序是为armeabi v7a with霓虹灯支持 它在 NVIDI
  • 当 mov 指令导致页面错误并且在 x86 上禁用中断时会发生什么?

    我最近在自定义 Linux 内核 2 6 31 5 x86 驱动程序中遇到一个问题 其中 copy to user 会定期不将任何字节复制到用户空间 它将返回传递给它的字节数 表明它没有复制任何内容 经过代码检查 我们发现代码在调用 cop
  • 学习 (N)ASM 的最佳资源是什么? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想学习汇编已经有一段时间了 尽管我之前尝试过几次 但我还没有真正能够超越 Hello world 有
  • 从c调用汇编函数

    我试图从 c 调用汇编函数 但我不断收到错误 text globl integrate type integrate function integrate push ebp mov esp ebp mov 0 edi start loop
  • 对将英特尔傲腾 DC SSD 用作 IMDT 的额外 RAM 感到困惑吗? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我对英特尔傲腾 DC 有点困惑 我希望我的 Optane DC 能够同时充当 DRAM 和存储 一方面 我了解到只有 英特尔傲腾 DC 持
  • 使用 ACPI 在 MS-DOS 中关闭计算机

    我在基于 Pentium 的计算机上运行 MS DOS 6 22 主板支持 ACPI 并且想知道是否有一个可以用来关闭计算机的汇编语言例程 或者它是否比那个更难 即主板 具体的 基本上 我想创建一个小程序来从命令行关闭计算机 这是专门为此编
  • 如何反汇编、修改然后重新组装 Linux 可执行文件?

    无论如何 这可以做到吗 我使用过 objdump 但它不会产生我所知道的任何汇编器都可以接受的汇编输出 我希望能够更改可执行文件中的指令 然后对其进行测试 我认为没有任何可靠的方法可以做到这一点 机器代码格式非常复杂 比汇编文件还要复杂 实
  • x86 程序集 Pushl/popl 不适用于“错误:后缀或操作数无效”

    我是汇编编程的新手 正在努力解决编程基础 http savannah nongnu org projects pgubook 在带有 GNU 汇编器 v2 20 1 的 Ubuntu x86 64 桌面上 我已经能够汇编 链接执行我的代码
  • 将以下机器语言代码(0x2237FFF1)翻译成MIPS汇编

    到目前为止我已经翻译了这段代码 但我不明白的是如何计算 计算 16 位立即地址的数量 0x2237FFF1 转为二进制 0010 0010 0011 0111 1111 1111 1111 0001 现在我正在读取操作码 001000 并知
  • 在 qemu 中将扇区加载到 RAM

    我编写了一个简单的程序 将扇区 扇区编号 2 加载到 RAM 但什么也没打印 首先 我尝试了以下引导扇区代码 org 0x7c00 mov ax 0x1000 ES BX 1000 0000 mov es ax mov bx 0x00 Lo
  • movsbl指令的作用是什么? [复制]

    这个问题在这里已经有答案了 我在网上搜索过 但找不到明确的示例来理解该指令的作用 因此 如果有人可以举一个例子 这对我来说将会非常有帮助 用符号从字节扩展到长字移动 在Intel语法中 该指令的助记符是MOVSX 当变量类型为 C 时 C
  • 测试 xmm/ymm 寄存器是否为零的更快方法?

    It s fortunate that PTEST does not affect the carry flag but only sets the rather awkward ZF also affects both CF and ZF
  • INT 13h 无法读取超出特定扇区的数据

    我正在为我的操作系统编写内核 在将磁盘扇区加载到内存时遇到问题 以下是从磁盘加载扇区的函数代码部分 mov ax 0x3000 mov es ax mov ax 0x0201 mov bx word ptr bp 6 bx 0x000 0x
  • 在共享库中不使用 PLT 的情况下调用另一个目标文件中的函数?

    我有两个汇编代码 code1 s and code2 s我想从这两个构建一个可重定位 使用 fPIC 开关 共享库 I want code2 s调用一个函数 名为myfun1 其定义在code1 s 当我使用call myfun1 PLT
  • 32位进程在64位操作系统上可以访问多少内存?

    在 Windows 上 正常情况下 32 位进程只能访问 2GB RAM 或通过 boot ini 文件中的特殊开关访问 3GB 在 64 位操作系统上运行 32 位进程时 有多少可用内存 是否有任何特殊的开关或设置可以改变这种情况 默认
  • mfence 和 asm 易失性 ("" : : : "内存") 的区别

    据我了解 mfence是硬件内存屏障 而asm volatile memory 是编译器障碍 但是 可以asm volatile memory 用来代替 mfence 我感到困惑的原因是这个链接 http gcc gnu org ml gc
  • 跳转目的地太远:3 个字节

    我的循环有问题 其中包含的代码很长并且给了我错误jump destination too far by 3 byte s 当我删除时 mov edx offset str1 call writestring 这部分位于主过程下方 它不会给出
  • 气体:内存引用太多

    编译时指令如下 movl 4 ebp 8 ebp I got 内存引用过多 它出什么问题了 括号之前的数字是字节偏移量 这会导致发生内存引用 并且不能有两个movl 您需要先将值暂时移至寄存器 movl 4 ebp ecx movl ecx
  • 使用 GCC 生成可读的程序集?

    我想知道如何使用GCC http en wikipedia org wiki GNU Compiler Collection在我的 C 源文件中转储机器代码的助记符版本 这样我就可以看到我的代码被编译成什么 你可以使用 Java 来做到这一

随机推荐