现代x86中的“指令前缀”是什么意思?

21
为了解释为什么Bulldozer的表现不佳,我浏览了Agner Fog的优秀微架构书籍,在第178页中有这段话。 可以在一个时钟周期内解码具有多达三个前缀的指令。 带有超过三个前缀的指令会受到非常大的惩罚。 具有4-7个前缀的指令需要额外14-15个时钟周期来解码。 具有8-11个前缀的指令需要额外20-22个时钟周期,具有12-14个前缀的指令需要额外27-28个时钟周期。 因此,不建议使用超过三个前缀使NOP指令变长。 此规则的前缀计数包括操作数大小、地址大小、段、重复、锁定、REX和XOP前缀。 三字节VEX前缀计为一字节,而两字节VEX前缀不计入。 转义代码(0F、0F38、0F3A)不计入。
当我搜索前缀时,我遇到了非常技术性的定义,远远超出了我的能力范围。 或者,建议它们每个指令最多只能有4个前缀,这与上述摘录相冲突。
因此,简单地说,有人可以解释一下前缀是什么,它们是如何工作的,以及为什么你可能希望在一条指令上添加多达14个前缀而不是将其拆分?

非常好的问题。我想阅读Peter Cordes和其他专家在这里的答案。 - zx485
@zx485:就像大家所说的那样,通常只有在制作长时间的“NOP”时才会看到大量前缀。一个“NOP”的执行时间不受长度影响,除了代码大小的副作用和前端问题。(正如Agner Fog的指南所解释的那样)。你绝对不想在使用uop-cache的CPU上浪费14个NOP的空间。除此之外,x32 ABI通常使用地址大小前缀(因此基址+索引*比例寻址模式不会意外超出32位地址范围)。所以lock inc word [edi + r10d*4]需要4个前缀:lock op-sz addr-sz REX。 - Peter Cordes
如果我没记错的话,Atom和Silvermont有类似的解码器限制,但作为SIMD指令编码的一部分的前缀和转义字节是计入在内的。因此,它们在带有REX前缀的SSSE3及更高版本指令上可能会出现严重瓶颈。 - Peter Cordes
2个回答

17

通常,您可以根据指令和操作数的意图使用任意数量的前缀。汇编器会自动添加一些前缀,而其他前缀需要手动使用。

他们提到的情况是多字节NOP,传统上用于对齐填充,其思想是使用单个但适当长度的指令来节省资源。显然,使用更多的前缀仅为了使它成为单个指令,可能比使用带有较少前缀的两个指令性能更差。

此规则的前缀计数包括操作数大小、地址大小、段、重复、锁定、REX 和 XOP 前缀。三字节的 VEX 前缀算作一个前缀,而两字节的 VEX 前缀不算。

例子:

  • 操作数大小:可以在32位和16位寄存器之间切换,例如mov ax,[foo]mov eax,[foo]相同,但使用前缀66h
  • 地址大小:可以在32/16或64/32位地址大小之间切换,例如mov [eax],foomov [rax],foo相同,但使用前缀67h(在64位模式下)
  • 段:可以覆盖所使用的段,例如mov [fs:eax],foomov [eax],foo相同,但使用前缀64h
  • 重复:与字符串指令一起用于重复执行,例如rep cmpsbcmpsb相同,但使用前缀f3h
  • 锁定:与某些指令一起使用,使它们成为原子操作,例如lock add [foo],1add [foo],1相同,但使用前缀f0h
  • REX.W :用于切换到64位操作数大小,例如add rax, 1与带有前缀48hadd eax, 1被编码为相同的指令。
  • REX.R,B,X:作为modr/m字节的扩展来访问额外的寄存器。例如add r8d, 1与带有前缀41hadd eax, 1被编码为相同的指令。
  • XOP,VEX:用于向量指令子集。

  • 8
    “四个前缀”的概念来自于“前缀组”:
    1. lock/rep/repne 2. 段寄存器 3. 操作数大小覆盖 4. 地址大小覆盖
    可以重复使用前缀,但不能从同一组中使用多个不同的前缀(可以这样做,但行为未定义)。虽然只适用于1和2组,但其他组每个组中只有一个内容。
    66 66 66 66 66 66 66 66 90 这样的代码是有效的(但解码可能较慢), 而像 2E 3E 00 00 (混合段覆盖)就无效了。
    在执行字节时堆叠前缀可用于代码对齐,与使用nop填充不同,它不会造成执行时间的损失。但同时使用太多的前缀会增加解码时间。

    问题断言:具有12-14个前缀的指令需要额外27-28个时钟周期。如果要编码一个没有冗余的具有12-14个前缀的操作码,你会怎么做?我只是好奇想要一个例子。 - zx485
    3
    @zx485,你不能。 “四个前缀”规则意味着最多只能有4个前缀是非冗余的,如果超过了这个数量,就必须包括冗余前缀。在任何情况下,遗留前缀只有11种,即使考虑到无效组合,也不能拥有12个非冗余前缀(除非添加新的REX或VEX,但它们有不同的规则)。 - harold
    感谢您的回答。当然,这意味着任何具有最多三个前缀的(合理/非冗余)指令/操作码在处理时不会有超过1个周期的惩罚。这是一个好消息 :-) 应用所有四个前缀应该是极为罕见的 - 因此是可以接受的。 - zx485
    @zx485 当然,在32位和64位代码中,使用旧前缀已经相当罕见了,这使得它变得更不重要。 - harold
    很容易想象一些使用lock inc word [edi + r10d*4]的Linux x32代码。gcc在x32中经常使用地址大小前缀,以防止寄存器高位有垃圾数据或者在有效地址内的地址计算不截断到32位会产生不同的结果时。由于有lock存在,Bulldozer上的前端气泡问题可能不是一个因素。类似的指令使用fs段重写(用于线程局部存储)可能会更成为一个问题,因为它可以使用4个前缀而没有lock,因此不会变得超级慢。 - Peter Cordes

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接