分支预测器是否也在其预测中包含 I/O 指令?
Does the branch predictor also include I/O instructions in its prediction?
我目前正在编写一个 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 读取,分支预测器将用循环指令填充管道:经过一些迭代后,它会注意到总是有一个分支,类似于 the case here.
如果分支预测器在其预测中确实包含 I/O 条指令(我不确定),则上述内容是正确的。
那么分支预测器是否会使用 I/O 指令的结果进行自我调整,就像不可预测的内存读取一样?还是这里发生了其他事情?
pause
在这里有意义吗?
分支预测器不在预测中包含任何其他指令。它只是根据分支指令本身 and/or 来猜测其先前的分支历史。 None 循环中的其他指令,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。
并不是说它真的很重要。在现代处理器上,键盘控制器发送或接收单个字节需要超过一百万个周期,甚至几百个周期,因为一些最坏的情况管道停顿并不重要。
我目前正在编写一个 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 读取,分支预测器将用循环指令填充管道:经过一些迭代后,它会注意到总是有一个分支,类似于 the case here.
如果分支预测器在其预测中确实包含 I/O 条指令(我不确定),则上述内容是正确的。
那么分支预测器是否会使用 I/O 指令的结果进行自我调整,就像不可预测的内存读取一样?还是这里发生了其他事情?
pause
在这里有意义吗?
分支预测器不在预测中包含任何其他指令。它只是根据分支指令本身 and/or 来猜测其先前的分支历史。 None 循环中的其他指令,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。
并不是说它真的很重要。在现代处理器上,键盘控制器发送或接收单个字节需要超过一百万个周期,甚至几百个周期,因为一些最坏的情况管道停顿并不重要。