ZHCU881D May 2020 – May 2024
像 C7000 这样的超长指令字 (VLIW) 数字信号处理器 (DSP) 依靠循环的软件流水线实现性能最大化。软件流水线 是一种方法,其中源循环的连续迭代是重叠的,以便在整个循环中尽可能多的周期里利用 CPU 上的功能单元。
下图显示有软件流水线和无软件流水线的情况下循环迭代的执行。您可以看到,在没有软件流水线的情况下,循环被调试,因此循环迭代 i 完成之后迭代 i+1 才开始。在有软件流水线的情况下,迭代会重叠。因此,只要能够保持正确,即可在迭代 i 完成之前开始迭代 i+1。与其他方法相比,这种方法通常使机器资源的利用率更高。在软件流水线循环中,即使单个迭代可能需要 s 个周期才能完成,但每隔 ii 个周期就会启动一个新迭代。
在高效的软件流水线循环中,ii 远小于 s。ii 称为启动间隔,其是启动迭代 i 与启动迭代 i+1 之间的周期数。s 是完成第一个迭代所需的周期数,或者等于软件流水线循环的单次调度迭代的长度。
编译器尝试对最里面的源循环进行软件流水线处理。这些循环里面不再有任何其他循环。请注意,在编译过程中,软件流水线发生在内联之后和可将循环合并的循环转换之后,因此在某些情况下,您可能会看到编译器执行软件流水线的代码比预期的更多。
经过软件流水线处理后,循环有三个主要阶段,如下图所示:
以下示例显示了简单加权矢量和的源代码。
// weighted_vector_sum.cpp
// Compile with "cl7x -mv7100 --opt_level=3 --debug_software_pipeline
// --src_interlist --symdebug:none weighted_vector_sum.cpp"
void weighted_sum(int * restrict a, int *restrict b, int *restrict out,
int weight_a, int weight_b, int n)
{
#pragma UNROLL(1)
#pragma MUST_ITERATE(1024, ,32)
for (int i = 0; i < n; i++)
{
out[i] = a[i] * weight_a + b[i] * weight_b;
}
}
为了简化第一个软件流水线示例,使用了两个 pragma:
然后使用以下命令编译此代码:
cl7x --opt_level=3 --debug_software_pipeline --src_interlist --symdebug:none weighted_vector_sum.cpp
--symdebug:none
选项阻止编译器在汇编中生成调试信息和关联的调试指令。此调试信息与本文档的论述无关,如果包含该信息,则会不必要地延长此处所示的示例。一般不用关闭调试生成,因为调试信息的生成不会降低性能。
由于使用了 --src_interlist 选项,所以编译器生成的汇编文件未被删除,并包含以下内容:
;*----------------------------------------------------------------------------*
;* SOFTWARE PIPELINE INFORMATION
;*
;* Loop found in file : weighted_vector_sum.cpp
;* Loop source line : 10
;* Loop opening brace source line : 11
;* Loop closing brace source line : 13
;* Known Minimum Iteration Count : 1024
;* Known Max Iteration Count Factor : 32
;* Loop Carried Dependency Bound(^) : 0
;* Unpartitioned Resource Bound : 2
;* Partitioned Resource Bound : 2 (pre-sched)
;*
;* Searching for software pipeline schedule at ...
;* ii = 2 Schedule found with 7 iterations in parallel
;*
;* Partitioned Resource Bound(*) : 2 (post-sched)
. . .
;*----------------------------------------------------------------------------*
;* SINGLE SCHEDULED ITERATION
;*
;* ||$C$C36||:
;* 0 TICK ; [A_U]
;* 1 SLDW .D1 *D1++(4),BM0 ; [A_D1] |12|
;* || SLDW .D2 *D2++(4),BM1 ; [A_D2] |12|
;* 2 NOP 0x5 ; [A_B]
;* 7 MPYWW .N2 BM2,BM0,BL0 ; [B_N] |12|
;* || MPYWW .M2 BM3,BM1,BL1 ; [B_M2] |12|
;* 8 NOP 0x3 ; [A_B]
;* 11 ADDW .L2 BL1,BL0,B0 ; [B_L2] |12|
;* 12 STW .D1X B0,*D0++(4) ; [A_D1] |12|
;* || BNL .B1 ||$C$C36|| ; [A_B] |10|
;* 13 ; BRANCHCC OCCURS {||$C$C36||} ; [] |10|
;*----------------------------------------------------------------------------*
||$C$L1||: ; PIPED LOOP PROLOG
; EXCLUSIVE CPU CYCLES: 8
TICK ; [A_U] (R) (SP) <1,0>
|| SLDW .D1 *D1++(4),BM1 ; [A_D1] |12| (P) <1,1>
|| SLDW .D2 *D2++(4),BM0 ; [A_D2] |12| (P) <1,1>
MV .L2X A7,B0 ; [B_L2] |7| (R)
|| TICK ; [A_U] (P) <2,0>
MV .L2X A8,B1 ; [B_L2] |7| (R)
|| SLDW .D1 *D1++(4),BM0 ; [A_D1] |12| (P) <2,1>
|| SLDW .D2 *D2++(4),BM1 ; [A_D2] |12| (P) <2,1>
MV .S2 B0,BM2 ; [B_S2] (R)
|| MV .L2 B1,BM3 ; [B_L2] (R)
|| TICK ; [A_U] (P) <3,0>
MPYWW .N2 BM2,BM1,BL0 ; [B_N] |12| (P) <0,7>
|| MPYWW .M2 BM3,BM0,BL1 ; [B_M2] |12| (P) <0,7>
|| SLDW .D1 *D1++(4),BM0 ; [A_D1] |12| (P) <3,1>
|| SLDW .D2 *D2++(4),BM1 ; [A_D2] |12| (P) <3,1>
TICK ; [A_U] (P) <4,0>
MPYWW .N2 BM2,BM1,BL0 ; [B_N] |12| (P) <1,7>
|| MPYWW .M2 BM3,BM0,BL1 ; [B_M2] |12| (P) <1,7>
|| SLDW .D1 *D1++(4),BM0 ; [A_D1] |12| (P) <4,1>
|| SLDW .D2 *D2++(4),BM1 ; [A_D2] |12| (P) <4,1>
MV .D2 A6,D0 ; [A_D2] (R)
|| ADDD .D1 SP,0xfffffff8,SP ; [A_D1] (R)
|| TICK ; [A_U] (P) <5,0>
;** --------------------------------------------------------------------------*
||$C$L2||: ; PIPED LOOP KERNEL
; EXCLUSIVE CPU CYCLES: 2
ADDW .L2 BL1,BL0,B0 ; [B_L2] |12| <0,11>
|| MPYWW .N2 BM2,BM0,BL0 ; [B_N] |12| <2,7>
|| MPYWW .M2 BM3,BM1,BL1 ; [B_M2] |12| <2,7>
|| SLDW .D1 *D1++(4),BM0 ; [A_D1] |12| <5,1>
|| SLDW .D2 *D2++(4),BM1 ; [A_D2] |12| <5,1> BNL .B1 ||$C$L2|| ; [A_B] |10| <0,12>
|| STW .D1X B0,*D0++(4) ; [A_D1] |12| <0,12>
|| TICK ; [A_U] <6,0>
;** --------------------------------------------------------------------------*
||$C$L3||: ; PIPED LOOP EPILOG
; EXCLUSIVE CPU CYCLES: 7
;** ----------------------- return;
ADDD .D2 SP,0x8,SP ; [A_D2] (O)
|| LDD .D1 *SP(16),A9 ; [A_D1] (O)
|| ADDW .L2 BL1,BL0,B0 ; [B_L2] |12| (E) <4,11>
|| MPYWW .N2 BM2,BM0,BL1 ; [B_N] |12| (E) <6,7>
|| MPYWW .M2 BM3,BM1,BL0 ; [B_M2] |12| (E) <6,7>
STW .D1X B0,*D0++(4) ; [A_D1] |12| (E) <4,12>
ADDW .L2 BL1,BL0,B0 ; [B_L2] |12| (E) <5,11>
STW .D1X B0,*D0++(4) ; [A_D1] |12| (E) <5,12>
ADDW .L2 BL0,BL1,B0 ; [B_L2] |12| (E) <6,11>
STW .D1X B0,*D0++(4) ; [A_D1] |12| (E) <6,12>
RET .B1 ; [A_B] (O)
|| PROT ; [A_U] (E)
; RETURN OCCURS {RP} ; [] (O)
此汇编输出显示编译器生成的汇编文件中的软件流水线循环以及部分软件流水线信息注释块,其中包括关于各种循环特征的重要信息。
如果编译器成功地对循环进行软件流水线处理,那么编译器生成的汇编代码将包含软件流水线信息注释块,其中包含有关“ii = xx Schedule found with yy iterations in parallel”的消息。启动间隔 (ii
) 用于度量软件流水线循环开始执行循环新迭代的频率。启动间隔越小,执行整个循环所需的周期就越少。软件流水线循环信息还包括循环开始的源行、对循环资源和延迟要求的描述以及循环是否已展开(还有其他信息)。使用 –mw
编译时,该信息还包含单次调度迭代副本。
在本例中,实现的启动间隔 (ii) 是 2 个周期,并行运行的迭代次数为 7。
注释块还包括软件流水线循环的单次调度迭代 视图。从软件流水线循环的单次调度迭代视图中,可看到编译器如何转换代码以及编译器如何调度软件流水线循环的一次迭代与软件流水线中的迭代重叠。有关如何解释此注释块中的信息,请参阅节 5.2。