我在读 英特尔说明书 并且注意到有一个'NOP'指令在主CPU上什么都不做,还有一个'FNOP'指令在FPU上什么都不做。为什么有两个单独的指令什么都不做?

我看到的唯一不同的是他们抛出不同的异常,所以你可能会观察FNOP的异常来检测是否有可用的FPU。但是没有像CPUID这样的其他机制来检测这个吗?有什么实际的原因有两个单独的NOP指令?

有帮助吗?

解决方案

根据Raymond Chen和Hans Passant的评论,有两个单独的指令的历史原因,以及为什么它们不具有相同的效果。

两个指令都没有, NOPFNOP, ,最初被设计为一个明确的无操作指令。该 NOP 指令实际上只是指令的别名 XCHG AX,AX.(或在32位模式下 XCHG EAX, EAX.)在早期的英特尔处理器上,它实际上并没有做任何事情。虽然它没有外部可见的效果,但在内部它就像一个 XCHG 指令,采取尽可能多的周期来执行。486年是第一个专门处理它的英特尔CPU,它可以执行一个 NOP 在1个周期,而它花了3个周期来执行任何其他寄存器到寄存器 XCHG 指示。

治疗方法 XCHG AX,AX 指令在现代英特尔处理器中特别变得非常重要。如果它实际上仍然与自己交换相同的寄存器,它可能会引入流水线摊位,如果附近的指令也使用 AX 登记。通过特别对待它,CPU最终不会想到 NOP 需要等待设置的前一条指令 AX 或者下面的指令需要等待 NOP.

这带来了这样一个事实,即有很多不同的指令什么也不做 XCHG AX,AX 是唯一一个是单个字节(作为 交换寄存器与累加器单字节 XCHG 编码).通常这些指令被用作连续的单个指令的替代品 NOP 指令,例如出于性能原因对齐循环的开始时。例如,如果你想要一个6字节的NOP,你可以使用 LEA EAX,[EAX + 00000000].英特尔最终添加了一个显式的多字节NOP指令。(好吧,与其说是正式记录了从奔腾专业版开始就在那里的指令,不如说是增加了多少。)但是只有单字节形式被特殊处理;如果附近的指令使用相同的寄存器,则多字节Nop将产生摊位。

当AMD为他们的Cpu添加64位支持时,他们甚至走得更远。 NOP 不再等同于 XCHG EAX,EAX 在64位模式下。英特尔指令集的一个问题是有很多指令只修改寄存器的一部分。例如 MOV BX,AX 只修改低16位的 EBX 留下上层16位未修改。这些部分修改使CPU很难避免停滞,因此AMD决定在64位模式下使用32位指令时防止这种情况发生。每当一个32位操作的结果存储在一个(64位)寄存器, 该值为零扩展到64位,以便整个寄存器被修改.这意味着 XCHG EAX,EAX 不再是一个NOP,因为它清除了 EAX (因此,如果你明确地写 XCHG EAX,EAX, ,它不能组装到0x90,必须使用 87 C0 编码)。在64位模式下 NOP 现在是一个明确的NOP,没有其他解释。


至于 FNOP 指令,在原来的8087上,它并不完全清楚FPU如何处理这个指令,但我很确定它也没有作为一个明确的无操作处理。至少有一本旧的英特尔手册, ASM86语言再读手册 does document as doing something with no effect("将堆栈顶部存储到堆栈顶部")。从它在操作码映射中的位置,它看起来像它可能是一个别名 FST STFLD ST, ,两者都会将堆栈的顶部复制到堆栈的顶部。然而,它确实得到了一些特殊的处理,它平均在13个周期内执行,而不是堆栈堆叠的平均18或20个周期 FSTFLD 分别进行指令。如果它被视为无操作指令,我希望它会更快,因为有许多8087指令可以在一半的时间内执行。

更重要的是 FNOP 指令的行为不同于 NOP 因为FPU指令过去是如何在英特尔处理器上实现的。CPU本身不支持浮点算术,而是将这些任务卸载到可选的浮点协处理器上,最初是8087。协处理器的一个优点是它与CPU并行执行指令。但是,这意味着CPU有时需要等待FPU完成操作。在给它另一条指令之前,CPU会自动等待它完成前一条指令的执行,但是程序需要显式等待(使用 WAIT 指令)之前,它可以读取协处理器写入内存的结果。

因为协处理器是并行工作的,这也意味着如果一个FPU指令产生了一个浮点异常,当它检测到这个异常时,CPU就已经开始执行下一个指令了。通常,当一条指令在CPU上产生异常时,它在该指令仍在执行时被处理,但是当一条FPU指令产生异常时,CPU已经通过将该指令交给FPU来完成执行该指令。CPU只在显式或隐式等待协处理器时收到通知,而不是中断CPU并异步传递浮点异常。

在现代处理器中,FPU不再是协处理器,而是CPU的一个组成部分。这意味着程序不再需要等待FPU将值写入内存。但是FPU异常的处理方式没有改变。(事实证明,立即提供异常很难在现代Cpu上实现,所以他们利用了一个不必这样做的情况。)所以,如果以前的FPU指令产生了一个未传递的浮点异常,一个 NOP 离开异常未送达,而 FNOP, ,因为它是一个FPU指令,会做一个隐式的"等待",导致浮点异常被传递。

此示例演示了差异:

FLD1       ; push 1.0 onto the FPU stack
FLDZ       ; push 0.0
FDIV       ; divide 1.0 by 0.0
NOP        ; does nothing
NOP        ; does nothing
FNOP       ; signals a FP zero-divide exception and then does nothing
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top