我写的代码看起来像这样…
if(denominator == 0){
return false;
}
int result = value / denominator;
…当我想到CPU的分支行为时。
https://stackoverflow.com/a/11227902/620863此答案表示CPU将尝试正确猜测分支将走向哪条路,并且只有在发现它猜错分支时才会停止。
但是如果CPU不正确地预测了上面的分支,它将在接下来的指令中除以零。但这并没有发生,我想知道为什么?CPU实际上执行除零并在执行任何操作之前等待分支是否正确,或者它可以告诉它在这些情况下不应该继续?这是怎么呢
当基于预测推测性地执行分支时,CPU可以自由地做任何它想做的事情。但它需要以一种对用户透明的方式来实现。因此,它可能会出现"除零"错误,但如果分支预测错误,这应该是不可见的。按照同样的逻辑,它可以将写操作暂存到内存中,但实际上可能不会提交它们。
作为一个CPU设计者,我不会费心预测过去的这种错误。这可能不值得。这个错误可能意味着一个错误的预测,很快就会自行解决。
这种自由是一件好事。考虑一个简单的std::accumulate
环路。分支预测器将正确预测大量跳转(for (auto current = begin, current != end; ++current)
通常跳转回循环的开始),并且有大量的内存读取可能存在潜在的故障(sum += *current
)。但是,如果CPU在前一个分支解决之前拒绝读取内存值,则会慢得多。然而,在循环末尾错误预测的跳转很可能导致无害的内存错误,因为预测的分支试图读取缓冲区之外的内容。这需要在没有明显错误的情况下解决。
不完全是。系统不允许在错误的分支中执行指令,即使它猜错了,或者更确切地说,如果猜错了,它必须不可见。基本的是:
- 在机器码的某处有一个测试。
- 处理器在其中一条可能的路径上加载带有指令的管道,并可能在内部执行 -根据MSalters的说法,一些处理器甚至可以同时执行路径(*)
- 如果它猜对了,很好,下面的指令已经在处理器缓存中预加载或已经执行,并且一切都尽可能快
- 如果它做出了错误的猜测,它只需要清理所有内容并重新启动正确的分支。
与参考文章的类比,如果开关位置不正确,列车必须立即停在路口,不能以错误的路径前往下一站,或者在此之前不能停车,则不允许乘客进出列车
(*)安腾处理器将能够并行处理多条路径。英特尔的逻辑是,他们可以制造宽处理器(并行处理很多事情),但他们在有效指令率上苦苦挣扎。通过推测性地执行两个分支,他们使用了大量的硬件(我认为他们可以运行2^N个分支,更深入一些),但它确实有助于明显的单核速度,因为它实际上总是用一个HW单位预测正确的分支-积分应该归功于MSalters的精度
但是如果CPU错误地预测了上面的分支,它就会除法在下面的说明中乘以零。但这并没有发生我在想为什么?
这很可能发生,但问题是:它是可观察到的吗?显然,这种推测性的除零不会也不应该使CPU"崩溃",但对于非推测性的除零也不会发生这种情况。在除零和流程退出并显示错误消息之间存在很长的因果链。它有点像这样(在POSIX, x86上):
- 负责除法的ALU或微码将除法被0标记为错误。 中断描述符#0被加载(int 0表示x86上的除零错误)。
- 一组寄存器(包括当前程序计数器)被压入堆栈。可能需要先从RAM中获取相应的缓存行。 中断处理程序被执行(一段内核代码)。在当前进程中引发SIGFPE信号。
- 最终,信号处理决定采取默认操作(假设您没有安装处理程序),即显示错误消息并终止进程。
- 这需要许多额外的步骤(例如使用设备驱动程序),直到最终用户可以观察到变化,即内存映射I/o的一些图形输出。
与一个简单、无错误的除法相比,这是大量的工作,而且其中很多可以推测执行。基本上直到实际的mmap'ed I/O,或者直到用于推测执行的有限资源集(例如影子寄存器和临时缓存线)耗尽为止。后者很可能会更快地发生。在这种情况下,需要挂起投机分支,直到确定是否实际占用了该分支并应该提交更改(一旦写入更改,则可以释放投机执行资源),或者是否应该丢弃更改。
重要的一点是:只要没有任何推测执行状态对其他线程、同一线程上的其他推测分支或其他硬件(如图形)可见,任何东西都将用于优化。然而,实际上,MSalters是绝对正确的,CPU设计人员不会关心为这个用例进行优化。因此,我同样认为,一旦设置了错误标志,真正的CPU可能只会挂起投机分支。如果错误是合法的,那么这最多只需要几个循环,而且即使这样也不太可能,因为您描述的模式是常见的。在此之后进行推测执行只会从更重要的情况中转移宝贵的优化资源。(事实上,如果我是一名CPU设计者,我想要快速处理的唯一处理器异常是一种特定类型的页面错误,其中页面是已知且可访问的,但"present"标志被清除,只是因为这种情况在使用虚拟内存时经常发生,并且不是真正的错误。但是,即使这种情况也不是特别重要,因为交换磁盘访问,甚至只是内存解压缩,通常都要昂贵得多。
除零并没有什么特别的。它是由ALU处理以产生某种效果的条件,例如为商赋一个特殊值。如果已启用此异常类型,它还可以引发异常。
与代码片段
比较if (denominator == 0) {
return false;
}
int result = value * denominator;
乘法可以推测地执行,然后在您不知情的情况下取消。除法也是一样。不用担心。