3.4. PMMX,PPro,P2及P3中的分支预测
BTB的组织
PMMX的分支目标缓冲(BTB)有256项,组织为16路*16组。每项由所属控制转移指令最后字节地址的2-31位识别。2-5位定义组,6-31位作为标签保存在BTB中。64字节间隔的控制转移指令有相同的组值,因此偶尔可能彼此挤出BTB。因为每组16路,这不太经常发生。
PPro,P2及P3的分支目标缓冲(BTB)有512项,组织为16路*32组。每项由所属控制转移指令最后字节地址的4-31位识别。4-8位定义组,所有的比特作为标签保存在BTB中。512字节间隔的控制转移指令有相同的组值,可能偶尔将彼此挤出BTB。因为每组有16路,这不太经常发生。
任何控制转移指令分配在第一次执行时,PPro,P2及P3为其分配一个BTB项。只要它跳转一次,它将待在BTB里,即使它不再跳转。在另一个具有相同组值的控制转移指令需要BTB项时,它可能会被挤出BTB。
误预测惩罚
在PMMX里,一个在U-管道里的条件跳转的误预测惩罚是4时钟周期,如果在V-管道里是5时钟周期。所有其他的控制转移指令,这是4时钟周期。
在PPro,P2及P3中,因为长的流水线,误预测惩罚要更高。一个误预测的代价通常在10到20个时钟周期。
条件跳转的模式识别
PMMX,PPro,P2与P3都使用如第6页所示的,带有4比特历史的双层自适应分支预测器。这个机制能良好地预测简单的重复模式。例如,一个交替采用两次、不采用两次的分支,在一小段学习时间后,将总是能预测。第7页的规则表明重复的分支模式可以被完美预测。所有周期不超过5的模式都能被完美预测。这意味着一个总是重复5次的循环将不会误被预测,但一个重复6次或更多的循环将不能被预测。
该分支预测机制也擅长处理“几乎规则的”模式,或规则模式的偏离形式。它不仅学习规则模式看起来像什么,它还学习规则模式的偏离形式看起来像什么。如果偏移形式总是相同的类型,那么它将记住在非规则事件后是什么,该偏离形式的代价将仅是一次误预测。类似的,在两个不同规则模式间来回切换的分支也能良好预测。
紧凑循环(PMMX)
在PMMX中,在紧凑循环里遇到下一个分支前,模式识别机制没有时间更新数据,分支预测不可靠。这意味着不能识别通常可以完美预测的简单模式。意外地,某些通常不能识别的模式,在紧凑循环里能完美预测。例如,在总是重复6次的循环底部的分支指令有分支模式111110。通常这个模式每次迭代有一到两次误预测,但在一个紧凑循环中,它没有。对重复7次的循环也一样。其他大多数循环次数的预测都比正常差。
要找出一个循环在PMMX上是否是“紧凑的”,你应该使用以下经验法则:统计循环中的指令数。如果指令数不超过6,那么循环将是紧凑的。如果有超过7条指令,可以合理地认为模式识别工作正常。很奇怪,不管每条指令要多少时钟周期,它是否暂停,以及它是否成对。复杂整数指令不统计。一个循环可以有大量复杂整数指令,而仍然表现得像一个紧凑循环。复杂整数指令是总是花费多个时钟周期,且不可成对的整数指令。复杂浮点指令与MMX指令仍然一一计数。注意,这个经验法则是启发性的,不完全可靠。
PPro,P2与P3上的紧凑循环正常预测,每次迭代最少两个时钟周期。
间接跳转与调用(PMMX,PPro,P2与P3)
对间接跳转与调用,没有模式识别,对一个间接跳转,BTB可以记住不超过一个目标。它简单地被预测为去到上一次的目标。
JECXZ与LOOP(PMMX)
在PMMX中,这两条指令没有模式识别。它们简单地被预测为去到上一次的目标。对PMMX,在时间关键代码中应该避免这两条指令,不过LOOP指令仍旧不如DECECX / JNZ。
3.5. P4与P4E中的分支预测
P4与P4E中分支目标缓冲(BTB)的结构细节未知。它有4096项,可能组织为8路*512组。它由追踪缓存里的地址索引,这些地址不一定与原始代码中的地址有简单的对应关系。因此,让程序员预测或避免BTB竞争是困难的。在P4与P4E中不预测长跳转,调用及返回。
在第一次跳转时,处理器对任何附近的控制转移指令分配一个BTB项。永远不跳转的分支指令将排除在BTB之外,但仍在分支历史寄存器中。一旦它跳转了一次,它将待在BTB中,即使它再也不跳转。在另一个具有相同组值的控制转移指令需要BTB项时,它可能会被挤出BTB。所有条件跳转,包括JECXZ与LOOP,都对分支历史寄存器有所贡献。无条件及间接跳转,调用与返回不影响分支历史。
在P4与P4E上,分支误预测的代价比前代微处理器要高昂得多。从误预测恢复的时间很少小于24个时钟周期,通常在45个μops左右。显然,在到达回收阶段前,微处理器不能取消一个可疑的μops。这意味着如果你有许多长时延或低吞吐率的μops,那么误预测的惩罚可能高达100个时钟周期或更多。因此,组织代码使误预测最少十分重要。
P4中条件跳转的模式识别
P4使用带有16比特全局历史的“约定”预测器,如第8页解释的。根据ArsTechnica的一篇论文(J.Stokes: The Pentium 4 and the G4e: an Architectural Comparison: Part I.arstechnica.com, Mar. 2001),分支历史表有4096项。第8页上的预测规则告诉我们,P4可以预测任意周期不超过17的重复模式,以及某些具有更高周期的模式。不过,这适用于全局历史,而不是局部历史。因此,你必须看一下之前的分支,以确定一个分支是否可能被良好预测。我将使用下面的例子解释:
; Example 3.4. P4 loops and branches
mov eax, 100
A: …
…
mov ebx, 16
B: …
sub ebx, 1
jnz B
test eax, 1
jnz X1
call EAX_IS_EVEN
jmp X2
X1: call EAX_IS_ODD
X2: ….
mov ecx, 0
C1: cmpecx, 10
jnb C2
…
add ecx, 1
jmp C1
C2: …
sub eax, 1
jnz A
循环A重复100次。JNZ A指令被采用了99次,落空一次。在它落空时,它被误预测。循环B与C在循环A内部。循环B重复16次,不考虑之前的历史,我们期望它是可预测的。但我们必须考虑之前的历史。除了第一次,JNZB之前的历史(prehistory)看起来像这样:JNBC2:没采用10次,采用1次(没有统计JMPC1,因为它是无条件的);JNZA被采用;JNZB采用15次,没有采用1次。在全局历史表中,在JNZB没有被采用之前,总共17个被采用的连续跳转。因此,是每周期一到两次误预测。有一个方式避免这个误预测。如果你在标签A:与B:之间插入一个总是落空的伪分支,那么JNZB很可能被完美预测,因为现在之前的历史,在15次采用之前,有一次不采用。预测JNZB所节省的时间远超一个额外伪分支的代价。例如,这个伪分支可以是TESTESP, ESP / JC B。
JNZ X1每隔一次被采用,而与前面16个条件跳转时间无关,因此它不能被良好预测。
假定被调用的过程不包含任何条件跳转,JNBC2之前的历史如下:JNZB采用15次,不采用1次;JNZ X1采用或不采用;JNBC2:不采用10次,采用1次。因此JNBC2之前的历史总是唯一的。事实上,它有22个不同及唯一的之前历史,它将被良好预测。如果在C循环内有另一个条件跳转,例如,如果JMPC1指令是有条件的,那么JNBC2循环将不能良好预测,因为每个被采用的JNBC2之间将有20个实例。
一般而言,在P4上一个循环不能被良好预测,如果重复次数乘上循环内条件跳转数超过17。
交替分支
尽管上例中循环C是可预测的,循环B可以通过插入一个伪分支,成为可预测,JNZX1分支我们仍然有大问题。这个分支被交替采用与不采用,且与之前的16个分支事件无关。让我们研究一下在这个情形里预测器的行为。如果局部预测器以“弱不采用”状态开始,那么它将交替在“弱不采用”及“强不采用”间交替(参考图3.1)。如果全局模式历史表中的项在一个约定的状态中,那么分支每次将被预测为落空,我们将获得50%的误预测率(参考图3.3)。如果全局预测器恰好以“强不一致”状态开始,那么它将被预测为每次都采用,我们仍然将获得50%的误预测率。最后的情形是,如果全局预测器以“弱不一致”状态开始。它将在“弱一致”与“弱不一致”间交替,我们将得到100%的误预测率。没有办法控制全局预测器的起始状态,但我们可以控制局部预测器的起始状态。局部预测器以状态“弱不采用”或“弱采用”开始,根据在下面第28页解释的静态预测规则。如果我们交换这两个分支,并以JZ替换JNZ,使得该分支第一次就被采用,那么局部预测器将在状态“弱不采用”与“弱采用”间交替。全局预测器很快将进入状态“强不一致”,该分支将总是被正确预测。一个交替的后向分支应该组织成第一次不被采用,以获得相同的效果。除了交换这两个分支,我们可以在JNZX1前插入一个3EH预测提示前缀,将静态预测改变为“采用”(参考第28页)。这将有相同的效果。尽管这个控制局部预测器初始状态的方法解决了大多数情形下的问题,它不完全可靠。如果该分支在前面分支的一次误预测后被看到,它可能不工作。另外,序列可能被一个任务切换或其他将该分支挤出BTB的事件所破坏。我们没有办法在这样一个事件后,预测该分支第一次将被采用还是不采用。幸运的是,看起来设计者已经注意到这个问题,并实现了一个解决方法。在探索这些机制时,我发现一个未公开的前缀,64H,它在P4上获得成功。这个前缀不改变静态预测,但在第一个事件后控制局部预测器的状态,使得它将在“弱不采用”与“弱采用”状态间切换,不管该分支第一次是采用还是不采用。这个技巧可以被总结为以下规则:
一个间隔被采用且与前面16个分支事件无关的分支,在P4上如果带有一个64H前缀,可以被良好预测。这个前缀以如下方式编码:
;Example 3.5. P4 alternating branch hint
DB64H ; Hint prefix for alternating branch
jnzX1 ; Branch instruction
如果在16比特之前历史中该分支可以看到自己的前一个实例,不需要前缀。在之前的微处理器上,64H前缀没有作用,不会有影响。它是一个FS段前缀。64H前缀不能与2EH及3EH静态预测前缀一起使用。
P4E中条件跳转的模式识别
在P4E中分支预测比P4简单。不存在约定预测器,仅有一个16比特全局历史以及一个全局模式历史表。这意味着,如果重复次数乘上循环内条件跳转数不超过17,在P4E上一个循环可以被良好预测。
显然,设计者确定约定预测器对预测率的提升不足以抵消可观的复杂性。不过,看起来P4E从P4的约定预测器继承了一点独特性。64H前缀以,如果存在一个约定预测器,对一个交替分支可能是最优的方式,影响一个分支的前几次预测。没有有用的建议,但也不会造成严重问题。
3.6. 在PM及Core2里的分支预测
在PM与Core2里分支预测机制是相同的。
误预测惩罚
在PM中误预测惩罚大约是13个时钟周期,在Core则大约是15个时钟周期,对应流水线的长度。远程跳转,远程调用及远程返回不预测。
条件跳转的模式识别
相比之前的处理器,分支预测机制更先进。条件跳转由结合了双层预测器及循环计数器的混合预测器处理。另外,有一个机制用于预测间接跳转与间接调用。
一条分支指令,如果去一个方向n-1次,然后去另一个方向一次,被识别为有循环行为。如果周期n不超过64,循环计数器使得预测带有循环行为的分支成为可能。对每个分支保存循环计数器,而不使用全局历史表。取而代之,循环计数器有自己的128项缓冲。因此,循环的预测不依赖循环内其他分支的数目。嵌套分支被完美预测。
没有循环行为的分支使用带有8比特全局历史缓冲以及大小未知的历史模式表的双层预测器预测。不同于简单的循环模式,预测一个重复分支模式的能力,根据在第8页解释的带有全局历史表预测器的规则,依赖于循环内分支是数目。
元预测器确定一个分支是否具有循环行为,并相应选择预测机制。元预测器的机制未知。
间接跳转与调用的模式识别
如分支指令,使用相同的双层预测器原理预测间接跳转与间接调用(但不包括返回)。没有循环行为的分支与间接跳转/调用共享历史缓冲与历史模式表,但不共享BTB。每次跳转到一个新目标时,一个间接跳转/调用获得一个新的BTB项。可以有超过4个BTB项,即使该BTB仅有4路。历史缓冲为每个项保存多个比特,可能6或7个,以区分一个间接跳转/调用两个以上的目标。这使得预测在几个不同目标间切换的周期性跳转模式成为可能。我看到的能完美预测的最大不同目标数是36,不过这样令人印象深刻的预测很少见,原因在下面解释。如果所有长度8的历史子模式是不同,一个周期性模式可以被预测。这还改进了后续条件跳转的预测,因为它们共享相同的历史缓冲。一个条件跳转可以构成一个基于前面间接跳转不同目标间差别的预测。
上述观察表示历史缓冲必须具有至少8*6=48比特,但一个248项的历史模式表物理上是不可能的。48或更多的比特必须以某种哈希算法压缩为x位键值,以索引历史模式表的2x项。X的值未知,但它可能在10到16之间。哈希算法可能是历史缓冲的比特与分支指令地址或BTB索引简单的XOR。更复杂的哈希函数也是可能的。
我的经验显示,在最里层循环有不少没有循环行为分支或间接跳转/调用的程序中,PM与Core2的误预测比预期多。对相同的试验,测试结果不总是相同的。显然存在对实验前BTB状态即历史模式表的依赖。
该设计对差劲预测给出了3个可能原因:第一个原因是对BTB项的竞争。第二个原因是哈希算法产生的键值别名导致的历史模式表项间的竞争。看起来这个别名现象不仅发生于间接跳转/调用,还出现于双层预测器预测的条件跳转。第三个可能的原因是元预测器差劲的性能。我猜低于预期预测率的主要原因是历史模式表不够大。
BTB结构
在PM与Core2中看起来对不同的分支类型,有不同的分支目标缓冲。我在Core2上的测试显示以下BTB结构:
对无条件跳转及没有循环行为的分支:
4路512组 = 2048项。每项由所属控制转移指令最后字节地址的0-21比特识别。4-12比特定义组,余下比特作为标签保存在BTB中。仅22-31比特不同的项竞争相同的BTB项。控制转移指令在第一次遇到时就分配一个BTB项。
对具有循环行为的分支:
2路64组 = 128项。每项由所属控制转移指令最后字节地址的0-13比特识别。4-9比特定义组。仅14-31比特不同的项竞争相同的BTB项。
对间接跳转与间接调用:
4路2048组 = 8192项。每项由所属控制转移指令最后字节地址的0-21比特识别。0-10比特定义组,余下比特作为标签保存在BTB中。仅22-31比特不同的项竞争相同的BTB项。
对近程返回:
返回栈缓冲有16项。
应该注意到这些数据是不太确定的,因为我对没有循环行为分支的测量是不一致的。这个不一致可能归因于历史模式表的哈希函数是未知这个事实。
(文献:S.Gochman, et al.: The Intel Pentium M Processor: Microarchitecture andPerformance. Intel Technology Journal, vol. 7, no. 2, 2003)。