Intel, AMD及VIA CPU的微架构(23)

8. Core 2与Nehalem流水线

名为“Intel Core 2”的微架构是PM设计的进一步发展。流水线被扩展,以处理每时钟周期4个微操作,执行单元从64位扩展到128位。在2008年引入的45nm版本比之前的65nm版本有更快的除法以及混排操作。

现在Core微架构与其衍生产品构成了所有Intel x86处理器的基础,包括移动、桌面及服务器处理器。Intel Core 2处理器有两个或更多带有独立1级缓存以及共享2级缓存的CPU核。Nehalem有独立的1级与2级缓存,以及一个共享的3级缓存。

Nehalem在4个CPU核中的每一个都能运行两个线程,总共8个线程。

所有的Core 2处理器都支持x64,SSE3与补充SSE3指令集。在45nm版本上支持SSE4.1。在Nehalem上支持SSE4.2。

Core有与PM相同的节能特性。在不使用时,它可以关闭部分内部总线,总线单元等。在工作负荷低时,时钟频率被降低。在不使芯片过热的前提下,这些节能特性对提高时钟频率有积极的影响。Nehalem可以在其他核空闲时,提高一个核的时钟频率。

8.1. 流水线

Core流水线非常类似于Pentium M的,但每样东西更多,将吞吐率从每时钟周期3个μop提升为4个。先进的节能技术使得采用高时钟频率,而不会使芯片过热,成为可能。NetBurst架构的追踪缓存被废弃,取而代之的是传统的代码缓存。

Core 2微架构据说有一个仅有14级的流水线,以减少功耗、推测执行与分支误预测的惩罚。不过,我的测量显示在Core2里流水线大约比PM长2级。这个估计基于测量到的分支误预测惩罚至少是15,比PM多2个时钟周期,这个事实。寄存器待在ROB的时间,由寄存器读暂停测量(第66页),大约比PM多了2个时钟周期,Core2上部分标记暂停比PM至少多1个时钟周期。这些测量与观察到的指令获取与回收的改进一致。很可能一个额外的流水线阶段被用于改进指令获取与预解码,而一个用于改进回收。Nehalem的流水线至少长了2级,分支误预测惩罚至少是17。

在Core 2中重排缓冲有96项,Nehalem是128项。根据Intel的公开信息,在Core 2中保留站有32项,Nehalem是36项。

8.2. 指令获取与预解码

通过在分支预测与指令获取间增加一个队列,改进了指令获取。在许多情形中,这可以消除被采用分支中的时延空泡。不幸地,获取带宽仍然局限在每时钟周期16字节。如下面解释的,限制瓶颈是预解码器。

指令解码机构被分解为一个预解码器与解码器,之间有一个队列。在Core 2中,这个队列的实际大小是64字节。预解码器的主要目的是检测每条指令在何处开始。这相当困难,因为每条指令的长度从1到15字节,要确定其长度并且知道下一条指令在何处开始,需要查看一条指令的几个字节。预解码器也识别指令前缀以及指令的其他组成。

预解码器的最大吞吐率是每时钟周期16字节或6条指令,取决于谁最小。流水线余下部分的吞吐率通常是每时钟周期4条指令,或在宏融合(参考下面第95页)时5条。显然,如果在每个16字节代码块中少于4条指令,预解码器的吞吐率少于每时钟周期4条指令。因此,平均代码长度最好小于4字节。

如果在16字节代码块中有超过6条指令,预解码器的吞吐率也会降低。原因是,直到前面的块耗尽时,预解码器才会载入新的16字节代码块。如果在16字节代码块中有7条指令,在第一个时钟周期,预解码器将处理前6条指令,在下一个时钟周期里处理最后的指令。这给出了小于理想的、每时钟周期3.5条指令的平均预解码器吞吐率。因此,每16字节代码块的最优指令数是5或6,对应平均指令长度约为3字节。任何跨越16字节边界的指令将被留下,直到处理下一个16字节块。调整指令长度,以获取每16字节块最优指令数可能是必要的。如何加长或缩短指令的讨论,参考手册2“优化汇编例程”。

回路缓冲

在Core2中的解码器队列可被用作一个64字节、18条指令的回路缓冲(loopback buffer)。解码器队列中的预解码指令可以被重用,只要分支指令回环到一条仍在缓冲中的指令。因此,对完全包含在64字节缓冲的小循环,预解码不是瓶颈。

Core2回环缓冲几乎就像一个64字节的0级代码缓存,组织为4行16字节。可以完全包含在4个16字节对齐块里的循环最多可以每时钟周期32字节的速率执行。四个16字节块甚至不需要连续。包含跳转的循环(但不包含调用与返回),如果循环内代码可以包含在4个对齐16字节块中,仍然可以超出预解码器吞吐率。

Nehalem设计稍有不同。Core2在预解码器与解码器间有循环缓冲,而Nehalem在解码器后有循环缓冲。Nehalem循环缓冲可以保存28个(可能融合的)μop。循环代码的大小被限制在256字节代码,或最多8个32字节块,包含超过256字节的循环不能使用循环缓冲。在这种循环处理中有一个时钟周期的时延,因此包含4*N(可能融合的)μop的循环将需要N+1个时钟周期来执行,如果没有其他瓶颈。Core2没有这一个时钟周期的时延。

在预解码或解码是瓶颈的情形下,即指令平均长度超过4字节或包含长度改变前缀,循环缓冲可以显著加速执行。因此,关键循环最好在16字节边界对齐,在Core2上不超过16字节或18条指令,Nehalem上不超过256字节或28条指令。

长度改变前缀

对改变后续操作码字节的含义,使得指令长度改变的特定前缀,指令长度解码器会有问题。这称为长度改变前缀。

例如,指令MOV AX, 1,在32位及64位模式中,有一个操作数大小前缀(66H)。没有操作数大小前缀的相同代码表示MOV EAX, 1。指令MOV AX, 1有2字节立即数来表示16位值1,而MOV EAX, 1有4字节立即数表示32位值1。因此,操作数大小前缀改变了指令余下部分的长度。预解码器不能在一个时钟周期里解决这个问题。从这个错误恢复,它需要6个时钟周期。因此,避免这样的长度改变前缀非常重要。Intel文档宣称,如果指令跨越16字节边界,长度改变前缀的惩罚增加到1个时钟周期,但我不能证实。我的测量还显示在这个情形下6个时钟周期的惩罚。如果在16字节块中有超过4条指令,惩罚可能小于6个时钟周期。

有两个前缀会导致这个问题。这是操作数大小前缀(66H)以及少用的地址大小前缀(67H)。操作数大小前缀改变指令的长度,导致在32位及64位模式下,以下情形中的延迟:

  • 如果MOV或TEST指令有一个16位目的操作数以及一个作为源操作数的立即数常量。例如MOV AX, 1。这可由MOV EAX, 1替换。
  • 如果其他指令有一个16位目的操作数以及一个作为源操作数的立即数常量,且该常量不能表示为8位有符号整数。例如ADD AX, 200。应该由ADD EAX, 200替换。但ADD AX, 100不会有一个长度改变前缀,因为100在8位有符号整数范围内。
  • 如果指令NEG,NOT,DIV,IDIV,MUL及IMUL之一有一个16位操作数,且操作码字节与mod-reg-rm字节间是16字节边界。这些指令有一个假的长度改变前缀,因为这些指令具有与带有16位立即数的TEST指令相同的操作码,区别包含在mod-reg-rm字节中的reg比特中。因此,解码器不能确定该前缀是否为长度改变前缀,直到它读下一个16字节代码块。如果你不能控制16字节边界的位置,可能希望避免使用这些指令的16位版本。

这些规则也适用于16位模式中的32位操作数。可使用objconv实用程序中的反汇编器来检测这些长度改变前缀。

地址大小前缀(67H)总是导致16位及32位模式下,任何具有一个mod/reg/rm字节指令的时延,即使它不改变指令的长度。唯一使用67H前缀合理且不导致暂停的指令是JCXZ/JECXZ/JRCXZ,字符串指令与XLAT。在64位模式下,地址大小前缀不改变长度,不会导致时延。地址大小前缀通常被视为过时的,应该避免。

在带有64位立即数的MOV指令中REX.W前缀(48H)也会改变指令长度,但预解码器可以无碍地解决这个问题。

在适合回环缓冲的循环里,长度改变前缀的代价仅在第一次出现,因为这个缓冲包含预解码或已解码指令。

​​​​​​​8.3. 指令解码

在Core2与Nehalem中,指令解码非常类似于之前的处理器,如63页解释的那样,但从三个解码器扩展为四个,使得它每时钟周期可以解码4条指令。第一个解码器可以在一个时钟周期里解码任何最多生成4个μop的指令。其他三个解码器仅能处理产生一个μop的指令。除此之外,这些解码器可以处理的指令没有其他限制。解码器的最大吞吐率是每时钟周期7个μop,如果指令被组织为4-1-1模式,使得第一个解码器产生4个μop,其他三个解码器每个产生1个μop。解码器的输出最小是每时钟周期2个μop,如果所有的指令都产生2个μop。在这个情形下,仅第一个解码器是活动的,因为其他三个解码器不能处理产生多个μop的指令。如果代码包含许多产生2-4个μop的指令,那么这些指令应该被2到3个单μop指令隔开,以优化解码器吞吐率。产生超过4个μop的指令使用微代码ROM,且需要多个时钟周期解码。每条指令产生的μop数列在手册4“指令表”中。与解码器吞吐率相关的数据是列在“μop融合域”下的数字。

解码器每时钟周期可以从64字节的缓冲读两个16字节代码块,因此在一个时钟周期里总共可以解码32字节。但预解码器的输出被限制为每时钟周期16字节或更少,因此,仅在线性代码里,在前面时钟周期内处理少于16字节时,解码器在一个时钟周期里能收到超过16字节。

之前的处理器对解码器每时钟周期可以处理的指令前缀数有限制。Core2页Nehalem没有这样的限制。带有任何数量前缀的指令可由任一解码器在一个时钟周期内解码。仅有的限制来自指令集定义,它将指令加前缀的长度限制在15个字节。因此,在一个时钟周期里,解码一条带有14个前缀的单字节指令是可能的。当然,没有指令需要这么多前缀,但重复的前缀可用来代替NOP,作为对齐后续循环入口的填充。重复前缀的讨论,参考手册“优化汇编例程”。

8.4. 微操作融合

Core2与Nehalem以与PM相同的方式使用μop融合,如第80也所述。某些在执行单元里需要两个μop的指令可以使用μop融合技术,将两个μop合为一个来通过流水线的大部分,以节约流水线带宽。融合的μop被调度器处理为两个μop,并提交给两个执行单元,但在流水线的其他阶段,它都被视为一个μop,并且在重排缓冲中仅占用一个项。μop融合也提升解码吞吐率,因为融合μop可由仅能处理单μop指令的解码器产生。

μop融合有两种情形:读-修改指令与写指令。读-修改指令需要一个μop来读内存操作数,另一个μop来执行这个操作数的计算。例如,ADD EAX, [MEM]需要一个μop读MEM,一个μop将这个值加到EAX上。这两个μop可以融合为一个μop。写指令需要一个μop计算地址,一个μop写该地址。例如,MOV [ESI+EDI], EAX需要一个μop计算地址[ESI+EDI]。一个μop将EAX保存到这个地址。这两个μop可以融合起来。Core2与Nehalem可使用μop融合的情形比PM要多。例如,读-修改-写指令可使用读-修改融合与写融合。大多数XMM指令可以使用μop融合,但不是所有。

一个融合μop可以有三个输入依赖,而非融合μop仅能有两个。写指令可能有三个输入依赖,例如MOV [ESI+EDI], EAX。这是为什么写指令被分解为两个μop,而读指令仅有一个μop的原因。

同时有rip-相对取址与立即数的指令不能使用μop融合。例如,CMP BYTE PTR [RIP+m], AL可以融合,但CMP BYTE PTR [RIP+m], 1不能。

可以在手册4“指令表”里查找哪些指令使用μop融合。使用μop融合的指令在“非融合域”下列出的μop数比“融合域”下列出的μop数多。

8.5. 宏操作融合

在少数情形下,Core2与Nehalem还可以将两条指令融合为一个μop。这称为宏操作融合(macro-op fusion)。在某些情形中,解码器可以将一条比较或测试指令与一条后续的条件跳转指令融合为单个比较-分支μop。在执行单元中,比较-分支μop不会被分解为两个μop,但在执行端口5作为单个μop执行。这意味着在流水线的所有阶段,从解码到回收,宏操作融合都会节省带宽。不过,如果预解码是瓶颈,宏操作融合也无济于事。

仅当以下条件都满足时,宏操作融合才是可能的:

  • 第一条指令是CMP或TEST,第二条指令是条件跳转指令,除了JECXZ及LOOP。
  • CMP或TEST指令可以有两个整数操作数,一个寄存器及一个立即数,一个寄存器及一个内存操作数,但不能是一个内存操作数与立即数。
  • Core2仅在16位与32位模式中可以进行宏操作融合。Core Nehalem还可以在64位模式中这样做。
  • 测试零标记及/或进位标记的分支(JE,JNE,JB,JBE,JA,JAE)可与前面的CMP或TEST融合。这包括所有无符号比较。有符号比较的分支(JL,JLE,JG,JGE),在Core Nehalem上,可与前面的CMP或TEST融合,但在Core2上仅能与TEST。仅测试溢出、奇偶位或符号位的分支(JO,JNO,JP,JNP,JS,JNS)可与TEST融合,但CMP不行。
  • 这两条指令间不能有其他指令(允许分支暗示前缀,但被忽略)。
  • 分支指令不应该在16字节边界处开始或跨越16字节边界。
  • 如果多个这样的指令对在同一时间到达四个解码器,那么仅第一对被宏融合。

任一解码器都可以进行宏操作融合,但不能同时。因此,在宏操作融合的情形下,在一个时钟周期里,看到4个解码器最多处理5条指令。

同时有微操作融合与宏操作融合是可能的。带有一个内存操作数与寄存器操作数,后跟一条分支指令的CMP或TEST指令可以产生包含所有三个操作的单个微宏融合μop:读,比较,分支。不过,一个μop可以包含多少信息是有限制的。这个限制很可能由重排缓冲(ROB)项的大小确定。一个ROB项没有足够的空间同时保存立即数,内存操作数地址,及分支目标地址。我猜这是为什么不能有同时有内存操作数与立即数的宏操作融合的原因。这也可能是为什么在Core2上宏操作融合不能工作的原因:64位分支地址在ROB项中占用更多空间。

程序员应该将CMP或TEST指令与后续的条件跳转指令保持在一起,而不是在中间调度其他指令;在一个比较-分支对与下一个比较-分支对间最好至少有三条其他指令,以利用宏操作融合。如果证实所有的操作数都不是负的,CMP后的分支指令最好是无符号类型。

在Core2上,64位模式中宏操作融合不能工作的事实,不应该使程序员抵制使用64位模式。由宏操作融合带来的性能提升不太可能超过几个百分点,且仅当μop吞吐率是瓶颈时。在可能性更大、瓶颈在别处的情形中,宏操作融合没有作用。

猜你喜欢

转载自blog.csdn.net/wuhui_gdnt/article/details/83412508