ZHCU881C May   2020  – December 2023

 

  1.   1
  2.   请先阅读
    1.     关于本手册
    2.     相关文档
    3.     商标
  3. 2引言
    1. 2.1 C7000 数字信号处理器 CPU 架构概述
    2. 2.2 C7000 分离式数据路径和功能单元
  4. 3C7000 C/C++ 编译器选项
    1. 3.1 概述
    2. 3.2 为性能选择编译器选项
    3. 3.3 了解编译器优化
      1. 3.3.1 软件流水线
      2. 3.3.2 矢量化和矢量谓词
      3. 3.3.3 自动使用流引擎和流地址生成器
      4. 3.3.4 循环折叠和循环合并
      5. 3.3.5 自动内联
      6. 3.3.6 if 转换
  5. 4基本代码优化
    1. 4.1  迭代计数器和限制的有符号类型
    2. 4.2  浮点除法
    3. 4.3  循环携带依赖和 restrict (限制)关键字
      1. 4.3.1 循环携带依赖
      2. 4.3.2 restrict (限制)关键字
      3. 4.3.3 运行时别名消歧
    4. 4.4  函数调用和内联
    5. 4.5  MUST_ITERATE 和 PROB_ITERATE Pragma 与属性
    6. 4.6  if 语句和嵌套的 if 语句
    7. 4.7  内在函数
    8. 4.8  矢量类型
    9. 4.9  待使用和避免的 C++ 特性
    10. 4.10 流引擎
    11. 4.11 流地址生成器
    12. 4.12 优化库
    13. 4.13 存储器优化
  6. 5了解汇编注释块
    1. 5.1 软件流水线处理阶段
    2. 5.2 软件流水线信息注释块
      1. 5.2.1 循环和迭代计数信息
      2. 5.2.2 依赖和资源限制
      3. 5.2.3 启动间隔 (ii) 和迭代
      4. 5.2.4 常量扩展
      5. 5.2.5 使用的资源和寄存器表
      6. 5.2.6 阶段折叠
      7. 5.2.7 存储器组冲突
      8. 5.2.8 循环持续时间公式
    3. 5.3 单个调度迭代注释块
    4. 5.4 识别流水线故障和性能问题
      1. 5.4.1 阻止循环进行软件流水线作业的问题
      2. 5.4.2 软件流水线故障消息
      3. 5.4.3 性能问题
  7. 6修订历史记录

自动使用流引擎和流地址生成器

如果 C7100 和 C7120 器件上使用了 --auto_stream=no_saving 选项,或 C7504 和更高版本的器件上使用了 --auto_stream=saving 选项,编译器可以自动使用流引擎 (SE) 和/或流地址生成器 (SA)。

如果节 3.3.2 中的 weighted_vector_sum_v3.cpp 示例是使用 --auto_stream=no_saving 选项编译的,则会生成以下软件流水线信息块。(本例中生成的汇编适用于 C7100。)

;*   SOFTWARE PIPELINE INFORMATION
;*
;*      Loop found in file               : weighted_vector_sum_v2.cpp
;*      Loop source line                 : 7
;*      Loop opening brace source line   : 7
;*      Loop closing brace source line   : 9
;*      Loop Unroll Multiple             : 32x
;*      Known Minimum Iteration Count    : 32
;*      Known Max Iteration Count Factor : 1
;*      Loop Carried Dependency Bound(^) : 2
;*      Unpartitioned Resource Bound     : 2
;*      Partitioned Resource Bound       : 2 (pre-sched)
;*
;*      Searching for software pipeline schedule at ...
;*         ii = 2  Schedule found with 4 iterations in parallel. . .
;*----------------------------------------------------------------------------*
;*        SINGLE SCHEDULED ITERATION
;*
;*        ||$C$C36||:
;*   0              TICK                               ; [A_U]
;*   1              VMPYWW  .N2     VBM1,SE0++,VBL0   ; [B_N2] |8|  ^
;*     ||           VMPYWW  .M2     VBM0,SE1++,VBL1   ; [B_M2] |8|  ^
;*   2              VMPYWW  .N2     VBM1,SE0++,VBL0   ; [B_N2] |8|  ^
;*     ||           VMPYWW  .M2     VBM0,SE1++,VBL1   ; [B_M2] |8|  ^
;*   3              NOP             0x2               ; [A_B]
;*   5              VADDW   .L2     VBL1,VBL0,VB0     ; [B_L2] |8|
;*   6              VST16W  .D2     VB0,*D0(0)        ; [A_D2] |8|
;*     ||           VADDW   .L2     VBL1,VBL0,VB0     ; [B_L2] |8|
;*   7              VST16W  .D2     VB0,*D0(64)       ; [A_D2] |8| [C0]
;*     ||           ADDD    .D1     D0,0x80,D0        ; [A_D1] |7| [C1]
;*     ||           BNL     .B1     ||$C$C36||        ; [A_B] |7|
;*   8              ; BRANCHCC OCCURS {||$C$C36||}    ; [] |7|

在本例中,编译器使用 SE0 和 SE1 来替换之前设置了更低 ii 下限 4 的负载。通过使用 SE 执行这些负载,可以实现 2 ii。要使用上述示例中的 SE,编译器必须配置和打开它们。配置和打开操作显示在由循环之前的 --src_interlist 选项添加的注释中:

;***    -----------------------    S$1 = __internal_SE_TEMPLATE_1_i_1_i_d_i_d_i_d_i_d_i_d_2_4;
;***    -----------------------    S$1.ICNT0 = C$5 = (unsigned)(n+15&0xfffffff0);
;***    -----------------------    __se_open_V0_U32_O(*__se_mem((packed void *)a), 0, S$1);
;***    -----------------------    S$3 = __internal_SE_TEMPLATE_1_i_1_i_d_i_d_i_d_i_d_i_d_2_4;
;***    -----------------------    S$3.ICNT0 = C$5;
;***    -----------------------    __se_open_V0_U32_O(*__se_mem((packed void *)b), 1, S$3);

默认情况下,编译器仅在使用 SE 或 SA 看起来有利可图且合法时才使用它们。

就盈利能力而言,一个关键的考虑因素是使用 SE 或 SA 会带来处理开销;编译器未必知道此开销有利可图。在示例中,MUST_ITERATE pragma 指示最小迭代计数为 1024,这使编译器相信使用 SE 或 SA 可能有利可图,因此编译器执行转换。如果编译器未使用 SE 或 SA,而您希望编译器使用它们,则使用 MUST_ITERATE 或 PROB_ITERATE pragma 指示迭代次数会有所帮助。

出于合法性的考虑,不使用 SE 或 SA 的大多数原因与是否需要寻址模式可始终映射到 SE 或 SA 相关。这些原因包括但不限于:

  • 超过无符号 32 位类型范围的迭代计数器 (ICNT) 值。例如,当 iicnt 是 64 位类型时,for (i = 0; i < icnt; i++) 中会发生这种情况。
  • 超出有符号 32 位类型范围的 DIM 值。例如,当 dim 是 64 位类型时,data_in[i*dim] 中会发生这种情况。
  • 寻址中超出有符号 32 位类型范围的加法或乘法。例如,当 idim 是 64 位类型时,data_in[i*dim] 中会发生这种情况。
  • 寻址超出 INT_MIN 到 INT_MAX 个元素的范围的寻址。例如,在 int16_ptr[i] 中,当 int16_ptrint16 *iint 时,最大范围是 INT_MIN*16 个元素到 INT_MAX*16 个元素。

这些都是非典型情况,在实践中不太可能发生。要允许编译器忽略它们,请使用 --assume_addresses_ok_for_stream 选项。

如果使用 SE 或 SA 在实践中没有优势,您可以使用 FUNCTION_OPTIONS pragma 覆盖单个函数的 --auto_stream 和/或 --assume_addresses_ok_for_stream 选项。

如果代码在函数中显式使用 SE 或 SA,则编译器不会选择使用 SE 或 SA 进行优化。在这种情况下,编译器会假定代码使用该函数内的 SE 和 SA 处理优化的所有方面。

有关自动使用 SE 和 SA 以及相关编译器选项的详细信息,请参见 C7000 C/C++编译器用户指南 (SPRUIG8)。