我目前正在编写一个Intel 8042驱动程序并编写了两个循环,等待一些缓冲区可以使用:
/* Waits until the Intel 8042's input buffer is empty, i.e., until the
* controller has processed the input. */
i8042_waitin:
pause
in $i8042_STATUS, %al
and $i8042_STAT_INEMPTY, %al
jz i8042_waitin
ret
/* Waits until the Intel 8042's output buffer is full, i.e., data to read is
* available.
* ATTENTION: this here is the polling variant but there is also a way with
* interrupts! By setting bit 0 in the command byte you can enable an interrupt
* to be fired when the output buffer is full. */
i8042_waitout:
pause
in $i8042_STATUS, %al
and $i8042_STAT_OUTFULL, %al
jz i8042_waitout
ret
如您所见,我在循环中插入了pause
条指令。我最近才了解它,并希望自然而然地尝试一下
由于%al
的内容是不可预测的,因为它是I / O读取,分支预测器将使用循环的指令填充管道:在一些迭代之后,它将注意到总是采用一个分支,类似于{{3 }}
如果分支预测器在其预测中确实包含I / O指令,则上述情况是正确的,我不确定。
分支预测器是否使用I / O指令的结果调整自己,就像不可预测的内存读取一样?或者还有其他事情发生在这里?
pause
在这里有意义吗?
答案 0 :(得分:3)
分支预测器不包括预测中的任何其他指令。它只是基于分支指令本身和/或其先前的分支历史进行猜测。循环,PAUSE,IN或AND中的其他指令都不会对分支预测产生任何影响。
answer you linked中建议的PAUSE指令并不意味着影响分支预测器。这意味着防止在该问题的示例代码中由CMP指令访问的存储器位置被另一个处理器写入时发生的流水线停顿。 CMP指令也不会影响分支预测。
Peter Cordes提到,您可能会对CPU用于推测性地执行指令的不同技术感到困惑,以便尝试保持其管道已满。在您链接的问题中,推测性执行最终会影响自旋锁的性能。两者都有一个共同的根,CPU试图尽可能快地执行循环,但实际上影响自旋锁性能的是它从循环中出来的速度。只有循环最后一次迭代的速度很重要。
使用自旋锁代码进行推测性执行问题的第一部分是分支预测器将快速假设始终采用分支。在循环的最后一次迭代中,将会出现停顿,因为CPU将继续推测性地执行循环的另一次迭代。它必须抛弃它然后开始在循环外执行代码。但结果却更糟,因为CPU会推测性地读取CMP指令中使用的存储单元。因为它访问正常的内存,推测性读取是无害的,它们没有副作用。 (这与IN指令不同,因为从设备读取I / O会产生副作用。)这允许CPU推测性地执行循环的多次迭代。当另一个CPU更改内存位置时,这将使所有依赖于管道中的推测性读取的指令无效,因此执行自旋锁的CPU在从管道中清除它们时最终停止。
在您的代码中,我不认为PAUSE指令会改善循环的性能。 IN指令不访问普通内存,因此不会因为写入其他CPU的内存而导致管道被刷新。由于IN指令也不能被推测性地执行,因此在流水线中只能有一条IN指令,因此在循环结束时这个误预测分支的成本将相对较小。它可能具有该答案中提到的其他好处,可以减少功耗并为超线程处理器上的其他逻辑CPU提供更多的执行资源。
并不是说真的很重要。在现代处理器上,键盘控制器发送或接收单个字节需要超过一百万个周期,甚至几百个周期,因为一些最坏情况下的管道停顿并不重要。