从这里 - 分支预测问题,我开始编写程序的Python版本来检查Python中已排序/未排序版本的运行时.我先尝试排序.
这是代码:
import time
from random import *
arraysize = 327
data = []
for c in range(arraysize):
data.append( randint( 1 , 256 ) )
## !!! with this, the next loop runs faster
data.sort()
## test
start_time = time.clock()
sum = 0
for i in range(100000):
for c in range(arraysize):
if data[c] >= 128:
sum += data[c]
print time.clock() - start_time
Run Code Online (Sandbox Code Playgroud)
我不确定我的简单时序方法的准确性,但似乎已经足够了.当我设置时,arraysize = 32768我第一次等待> 20分钟!超过20分钟!但是arraysize = 327,我有时间8.141656691s.
如果我的代码中某处出错,请纠正我,或者在某种程度上使用Numpy/Scipy会加快速度.谢谢.
增加分支预测表的大小意味着程序中的两个分支不太可能共享共同预测器.预测单个分支指令的单个预测器通常比服务多个分支指令的相同预测器更准确.列出一系列分支采取和未采取的动作,以显示2位预测器共享的简单示例(几个不同的分支指令被映射到预测表的相同条目中),与情况相比,降低了分支误预测率其中单独的预测变量条目用于每个分支.(注意:请务必显示两个不同分支指令的结果,并明确指出这些结果的顺序以及它们对应的分支)
有人可以向我解释一下这个问题具体要求吗?此外,"2位预测器共享(几个不同的分支指令被映射到预测表的相同条目)"和"每个分支使用单独的预测器条目"是什么意思?我一直在阅读和重读我的笔记,但我无法理解.我试图在网上找到一些分支预测示例但是没有遇到任何问题.
如何以最好的便携方式对任意排序的数组执行(几乎)无分支的二分搜索?(例如,帮助编译器生成 CMOV 指令的代码对此非常有用。)
“几乎”是指“包含尽可能少的分支”。
我刚刚看到这篇文章:计算没有分支的两个整数的最小值或最大值
它始于"[o] n一些罕见的机器,其中分支是昂贵的......".
我曾经认为分支总是很昂贵,因为它经常迫使处理器清除并重新启动它的执行管道(例如,为什么处理排序数组比未排序数组更快?).
这给我留下了几个问题:
(x < y) ? x : y没有性能下降的情况下完成最小的分支?Math.min(...)功能就是那个三元语句......我相信在创建CPU时,当选择错误的分支时,分支预测是一个主要的减速.那么为什么CPU设计师选择一个分支而不是简单地执行两个分支,然后一旦你知道哪一个被选中就切掉一个?
我意识到这只能在短数量的指令中深入2或3个分支,或者并行阶段的数量会变得非常大,所以在某些时候你仍然需要一些分支预测,因为你肯定会遇到更大的分支,但是不会像这样的几个阶段有意义吗?在我看来,它会显着加快速度,值得增加一点复杂性.
即使只是一个分支深度几乎有一半的时间被错误的分支吃掉,对吧?
或者它可能已经有点像这样?当您进行组装时,分支通常只在两种选择中进行选择,对吗?
为什么分支预测准确?我们是否可以从较高的层面来思考代码的某些分支如何在 99% 的时间内执行,而其余的则是特殊情况和异常处理?
我的问题有点模糊,但我只对这个问题的高层观点感兴趣。让我举一个例子
假设你有一个带有参数的函数
void execute(Input param) {
assertNotEmpty(param)
(...)
}
Run Code Online (Sandbox Code Playgroud)
我有条件地执行我的函数,给定的参数不为空。99% 的情况下这个参数确实是非空的。那么我是否可以考虑基于神经网络的分支预测,例如,在某种程度上,由于它已经无数次看到这样的指令流(这样的断言很常见),它会简单地了解到大多数时候该参数非空并且相应地采取分支?
那么我们是否可以从以下角度来思考我们的代码:代码越干净,越可预测,甚至更常见,我们就越容易使用分支预测器?
谢谢!
我的理解是,在处理器流水线的开头,指令指针(指向要执行的下一条指令的地址)在读取后由分支预测器更新,以便可以在下一个周期获取该新地址.
但是,如果在管道的早期修改指令指针,这是否会影响当前处于执行阶段的指令,这些指令可能依赖于旧的指令指针值?例如,当执行call当前的EIP需要被推入堆栈时,但是当在分支预测期间更新指令指针时这不会受到影响吗?
这与这个问题有关
但是考虑一下,在现代的英特尔CPU上,SEC阶段是以微码实现的,这意味着将进行检查,从而使用烧入的密钥来验证PEI ACM上的签名。如果不匹配,则需要执行某些操作;如果不匹配,则需要执行其他操作。假定这是作为MSROM过程实现的,则必须有一种分支方式,但是鉴于MSROM指令没有RIP。
通常,当一个分支错误地预测到将要采取的指令然后退出时,ROB将检查异常代码,并因此将指令长度添加到ROB行的RIP或仅使用下一个ROB条目的IP,这将导致前端在分支预测更新中恢复到该地址。有了BOB,此功能现在已借给跳转执行单元。显然,这与MSROM例程不可能发生,因为前端与此无关。
我的想法是,有一条特定的跳转指令,只有MSROM例程才能发出,它会跳转到MSROM中的其他位置,并且可以进行配置,以便始终预测不采用MSROM分支指令,并且在分支执行单元遇到此指令时指令并执行分支,它会产生异常代码,并可能将特殊的跳转目标连接到它,并且在退出时会发生异常。另外,执行单元可以处理它,并且可以使用BOB,但我的印象是BOB由分支指令RIP索引,然后还存在这样一个事实,即通常会在退休时处理生成MSROM代码的异常。分支错误预测不需要我不认为的MSROM,而是所有操作都在内部执行。
我这里有一个测试题。
哪些指令可能会减慢处理器的工作速度,然后流水线不会预测(分支预测)进一步的执行方式?
可能的答案: JGE | 添加 | 订阅 | 推 | JMP | JNZ | 多| JG | 称呼
如果我们谈论分支预测,JGE、JMP、JNZ 和 JG 是要走的路吗?
在尝试 64 位 ARM 架构的过程中,我注意到根据是否使用br或ret用于从子例程返回而产生的特殊速度差异。
; Contrived for learning/experimenting purposes only, without any practical use
foo:
cmp w0, #0
b.eq .L0
sub w0, w0, #1
sub x30, x30, #4
ret
.L0:
ret ; Intentionally duplicated 'ret'
Run Code Online (Sandbox Code Playgroud)
该子例程的目的是通过返回到第一个调用的指令(即紧邻指向的指令之前的指令)来使调用者foo“重新输入”一次。通过一些粗略的计时,并且具有足够高的值,平均需要大约 1362 毫秒。奇怪的是,将第一个替换为使其运行速度提高了一倍,平均只需要 550 毫秒左右。foo w0foofoox30w0retbr x30
如果测试简化为仅使用裸ret/重复调用子例程,则时间差异就会消失br x30。是什么使得上面设计的带有 a 的子程序变慢ret?
我在某种 ARMv8.2 (Cortex-A76 + Cortex-A55) 处理器上对此进行了测试。我不确定 big.LITTLE 会在多大程度上扰乱时间,但它们在多次运行中似乎非常一致。这绝不是一个真正的[微]基准,而是一个“如果运行N次大约需要多长时间”的事情。