4. 改进向量处理器性能
在这一节里我们会展示五种改善向量处理器性能的技术。第一种技术,称之为链接(chaining),能够让一系列相互依赖的向量操作运行得更快。它起源于 Cray-1,但是现在大多数的向量处理器都支持这种技术。接下来两种技术通过引入新的向量指令类型来处理条件执行(conditional execution)和稀疏矩阵从而扩展可以被向量化的循环类型。第四种技术通过以增加道(lane)的方式增加更多的并行执行单元来提升向量处理器的峰值性能。第五种技术通过流水化以及重叠指令的启动来降低启动开销。
4.1 链接(chaining)--前推(forwarding)的概念在向量寄存器上的扩展
考虑一下简单的向量序列:
MULV.D V1, V2, V3
ADDV.D V4, V1, V5
在 VMIPS 中,就像我们看到的那样,这两条指令必须放到两个独立的 convoy 中,因为他们是互相依赖的。另一方面,如果我们把向量寄存器,在本例中即 V1,不看成一个单个个体,而是看成一组寄存器,那么所谓前推(forwarding)的概念就可以扩展以作用于向量的每个元素上。这一允许 ADDV.D 更早开始执行的电子称为链接(chaining)。Chaining 允许只要一个向量操作的源操作向量中的某个元素已经准备就绪时就开始执行该元素的操作:在链(chain)中,前一个功能单元的结果被 forward 到后一个功能单元。在实际中,chaining 通常是通过允许处理器同时读写同一个向量寄存器的不同元素来实现的。早期的 chaining 确实是以类似前推的方式工作的,但是这限制了链中源指令和目的指令的时序。近来的实现采用了灵活链接(flexible chaining)的方法,允许一个向量指令和任何别的活跃的向量指令链接,只要没有结构 hazard [1]。Flexible chaining 需要几条指令同时访问一个向量寄存器,这可以通过增加读写端口或者把向量寄存器文件组织成类似于内存系统里 bank 的形式类实现。我们在整个附录里就假定采用这种链接方式。
虽然一组操作互相依赖,但是 chaining 允许对于不同元素的操作并行执行。这使得这一组操作能够被调度到一个 convoy 中,从而减少 chime 的数目。对于前一个例子而言,可以达到持续达到每一个周期 2 个浮点操作,或者一个 chime,的速率(不考虑启动开销),即使他们是互相依赖的!它的总共执行时间为:
向量长度 + ADDV 的启动时间 + MULV 的启动时间
图 F.10 展示了上例链接和没有链接两个版本的情况,其中向量长度为 64。新的 convoy 仍然只需要一个 chime,但是因为他使用了 chaining,启动时间会很显著。在图 F.10 中,链接版本的总共执行时间为 77 个时钟周期,或者说平均一个结果 1.2 个周期。由于有 128 个浮点操作在其间执行,所以我们达到了 1.7 FLOPS 每周期。对于未链接版本,一共要花费 141 个周期,或者说 0.9 FLOPS 每周期。
图 F.10 链接和未链接版本的一个互相依赖的 ADDV 和 MULV 向量操作序列的时序。 图中 6 和 7 个时钟周期的延迟分别是加法和乘法的延迟。
虽然 chaining 通过把两个互相依赖的操作放置到同一个 convoy 的方式减少了以 chime 表示的执行时间,它并没有降低启动延迟。如果我们期望得到一个精确执行时间,我们就必须考虑启动开销。在 chaining 技术中,一个向量序列的 chime 数由不同的功能单元的个数以及程序所需的实际个数所决定。特别注意的是在任何 convoy 中都不能有结构 hazard。这意味着在 VMIPS 这样只有一个向量 load-store 单元的处理器上,如果一个程序有两个向量指令,那它必须至少占据两个 convoy,因此至少花两个 chime 的时间。
我们会在第六小节看到 chaining 对提升性能有极其重要的作用。实际上,chaining 是如此的重要以至于现在每一个向量处理器都支持 flexible chaining。
4.2 条件执行语句
根据 Amdahl 定律,我们知道如果一个程序可向量化部分很少或者不高,那么加速比是非常有限的。两个限制向量化的原因是在循环内部条件代码的存在以及稀疏矩阵的使用。在循环里包含 if 语句的程序不能在向量模式下执行,因为 if 语句引入了循环内部的控制依赖(control dependency)。同样的,稀疏矩阵也不能有效地利用我们之前讨论过的相关技术有效实现。我们在这一小节讨论如何处理条件执行,下一小节讨论稀疏矩阵。
考虑如下循环:
do 100 i = 1, 64
if (A(i).ne. 0) then
A(i) = A(i) - B(i)
end if
100 continue
这个循环由于包含有条件执行而不能正常地向量化。但是如果内层循环可以对 A(i) != 0 的部分进行迭代,那么减法操作就可以被向量化。在附录 G 中,我们看到了条件执行指令不是正常指令集的不一份。它们可以把控制依赖转换成为数据依赖,从而增加了循环的并行度。向量处理器可以类似地获益。
通常我们使用的一种扩展技术称为向量-掩码控制(vector-mask control)。Vector-mask control 利用一个长度为 MVL 的二值向量来控制向量指令的执行,就如同条件执行指令利用一个二值条件来决定一条指令是否执行一样。当向量-掩码寄存器(vector-mask register)被启用的时候,任何向量指令都只作用于向量掩码寄存器中相应元素值为 1 的那些向量元素上。在目的向量寄存器(destination vector register)中那些掩码寄存器里对应位置为 0 的元素不受向量操作的影响。如果向量-掩码寄存器由某个条件语句的结果来设置,那么只有满足条件的元素才会受到影响。把该寄存器清空表示将里面所有元素的值设为 1,使得后续的向量操作作用于所有的向量元素上。下面的代码可以用来实现前面的循环,假定 A 和 B 的起始地址分别在 Ra 和 Rb中。
LV V1, Ra ;load vector A into V1
LV V2, Rb ;load vector B
L.D F0, #0 ;load FP zero into F0
SNEVS.D V1, F0 ;set VM(i) to 1 if V1(i) != F0
SUBV.D V1, V1, V2 ;subtract under vector mask [2]
CVM ;set the vector mask to all 1s
SV Ra, V1 ;store the result in A
大多数最近的向量处理器都提供了向量-掩码控制。在这里描述的这种向量-掩码在大多数的处理器里都可以看到,但是另外一些允许向量掩码只作用于一部分的向量指令上。
然而,利用向量-掩码寄存器也有缺点。在讨论条件执行指令时,我们看到即使条件没有满足,该指令仍然需要花时间。但是,消除了分支和相应的控制依赖仍然使得条件指令执行地更快即使它得做一些没用的工作。同样的,应用向量掩码的向量指令即使对那些掩码值为 0 的元素而言也需要一定的执行时间。同理,即使大部分掩码值都为 0,使用向量-掩码控制仍然可能会比标量模式要快得多。实际上,在向量模式和标量模式之间巨大的潜在性能差距使得包含向量-掩码指令非常重要。
第二,在有些向量处理器中,向量掩码只用于禁止结果写回目的寄存器,而实际的计算操作会发生。这样的话,如果在前面例子中的操作是除法而不是减法,并且是对 B 而不是 A 进行条件测试,那么由于除数为 0 导致的浮点异常可能会发生。利用掩码同时屏蔽计算和写回的处理器可以避免这个问题。
---------------大家好,我是分割线---------------
[1] Flexible chaining 简单来讲就是放宽了 chaining 的限制。最初的 chaining 只能作用于相邻的两条指令之间。但是很有可能由于写程序的关系,原本可以 chaining 的两条语句被人为地分开了而不能利用 chaining 技术。Flexible chaining 允许这两条不相邻的指令重叠执行。
[2] 想一想这条指令硬件有可能是如何执行的?
No comments:
Post a Comment