我正在阅读Intel instruction manual并注意到有一个' NOP'在主CPU上执行任何操作的指令,以及“FNOP'在FPU上什么都不做的指令。为什么有两个单独的指令什么都不做?
我看到的唯一不同之处是它们会抛出不同的异常,因此您可能会注意FNOP的异常,以检测是否有可用的FPU。但是还没有像CPUID这样的其他机制来检测这个吗?有什么实际的理由可以有两个单独的NOP指令?
答案 0 :(得分:19)
扩展Raymond Chen和Hans Passant的评论,有历史原因有两个单独的指示以及为什么它们没有相同的效果。
这两条指令NOP
和FNOP
都不是最初设计为显式无操作指令。 NOP
指令实际上只是指令XCHG AX,AX
的别名。 (或者在32位模式下XCHG EAX, EAX
。)在早期的英特尔处理器上,它实际上什么都不做。虽然它没有外部可见效果,但内部执行就像XCHG
指令一样,需要执行多个周期。 ' 486是第一个专门处理它的Intel CPU,它可以在1个周期内执行NOP
,而执行任何其他寄存器到寄存器XCHG
指令需要3个周期。
在现代英特尔处理器中,专门处理XCHG AX,AX
指令非常重要。如果它实际上仍在与自身交换相同的寄存器,如果附近的指令也使用AX
寄存器,则可能引入流水线停顿。通过特别对待它,CPU最终不会认为NOP
需要等待先前设置AX
的指令或后续指令需要等待NOP
。
这表明有很多不同的指令什么都不做,尽管XCHG AX,AX
是唯一一个单字节的指令(作为exchange-register-with-accumulator single byte XCHG
encodings的一个特例) 。这些指令通常用作连续NOP
指令的单指令替代,例如出于性能原因而对齐循环开始时。例如,如果您想要一个6字节的NOP,则可以使用LEA EAX,[EAX + 00000000]
。英特尔最终添加了一个显式的多字节NOP指令。 (好吧,除了官方记录的自Pentium Pro以来一直存在的指令以外,没有那么多。)但是只有单字节形式被特别处理;如果附近的指令使用相同的寄存器,则多字节NOP将产生停顿。
当AMD为他们的CPU增加了64位支持时,他们甚至更进一步。在{64}模式下,NOP
不再等同于XCHG EAX,EAX
。英特尔指令集的一个问题是有很多指令只能修改寄存器的一部分。例如,MOV BX,AX
仅修改EBX
的低16位,保留高16位未修改。这些部分修改使CPU很难避免停顿,因此AMD决定在64位模式下使用32位指令时防止这种情况发生。只要32位操作的结果存储在(64位)寄存器the value is zero extended to 64-bits so that entire register is modified中。这意味着XCHG EAX,EAX
不再是NOP,因为它清除了EAX
的高32位(因此如果你明确地写XCHG EAX,EAX
,它就不能组装到0x90并且必须使用87 C0
编码)。在64位模式下,NOP
现在是一个明确的NOP,没有其他解释。
对于FNOP
指令,在原始的8087上,FPU对此指令的处理方式并不完全清楚,但我很确定它没有作为一个明确的处理也没有操作。至少有一本旧的英特尔手册,ASM86 Language Rerefence Manual确实记录了没有效果的事情("将堆栈顶部存储到堆栈顶部")。从它在操作码映射中的位置看起来它可能是FST ST
或FLD ST
的别名,两者都会将堆栈的顶部复制到堆栈的顶部。然而,它确实得到了一些特殊处理,它平均执行13个周期而不是平均18或20个周期,堆栈分别堆叠FST
或FLD
指令。如果它被视为无操作指令,我预计它会更快,因为有一些8087指令可以在一半的时间内执行。
更重要的是,FNOP
指令的行为与NOP
不同,因为过去如何在英特尔处理器上实现FPU指令。 CPU本身并不支持浮点算法,而是将这些任务卸载到可选的浮点协处理器上,最初是8087.协处理器的一个好处是它与CPU并行执行指令。但这意味着CPU有时需要等待FPU完成操作。在给出另一条指令之前,CPU会自动等待它完成前一条指令的执行,但程序需要在读取协处理器写入内存的结果之前明确等待(使用WAIT
指令)。 / p>
因为协处理器并行工作,这也意味着如果FPU指令产生了浮点异常,那么当它检测到这一点时,CPU就已经开始执行下一条指令。通常,当指令在CPU上生成异常时,在该指令仍在执行时处理该指令,但是当FPU指令生成异常时,CPU已经通过将其移交给FPU来完成执行该指令。 CPU不会以异步方式中断CPU并传递浮点异常,而是在显式或隐式等待协处理器时通知。
在现代处理器中,FPU不再是协处理器,它是CPU的组成部分。这意味着程序不再需要等待FPU将值写入内存。但是,如何处理FPU异常并没有改变。 (事实证明,立即提供异常很难在现代CPU上实现,因此他们利用了他们不必要的一个案例。)因此,如果先前的FPU指令生成了未传递的浮点异常,则{ {1}}保留未送达的异常,而NOP
,因为它是FPU指令,将执行隐含的"等待"这导致浮点异常被传递。
此示例演示了不同之处:
FNOP