使用全向量和 -opt-assume-safe-padding 选项

Vec BKM Utilize full-vectors by Document9800.00000000000

Vec BKM 使用全向量Document9800.00000000000

面向英特尔® MIC 架构的编译器方法

向量化要素使用全向量 -opt-assume-safe-padding 选项

高效的向量化需要充分利用向量硬件。这意味着用户应尽可能地在内核向量循环相对于剥离循环和/或剩余循环中执行最多的代码。

剩余循环

vec-loop trip-count 不是 vec-length长度的倍数时剩余循环执行剩余的迭代。尽管这在许多时候是无法避免的,在剩余循环上花费大量的时间会降低性能和效率。例如,如果 vec-loop trip-count 20 vec-length 16,则意味着内核循环每次执行时,剩余的 4 次迭代必须在剩余循环中执行。尽管 KNC 编译器能够对剩余循环进行向量化处理(正如 -vec-report6 所报告的),但是其效率却不如内核循环。例如,剩余循环将使用屏蔽,而且不得不使用收集/分散,而不是单位步长加载/存储(由于内存故障保护问题)。解决此问题的最好方法是以下这种方式重构算法/代码,即剩余循环不在运行时执行(使 trip-count 成为 vec-length 的倍数来实现),或者使 trip-count 大于 vec-length(这样便可降低剩余循环中的执行操作的开销)。

此外编译器优化还需要考虑实际的 trip-count 值。因此如果 trip-count 20那么相对于 trip-count n符号值 在运行时正好是 20 的情况也许是从一个文件读取的输入值),编译器在事先知道这一点的情况下对于编译器来说trip-count 在静态情况下是一个已知的常量会做出更好的决定。对于前者,您可以在执行循环之前在 C/C++ 中使用 "#pragma loop_count (20)" 或者在 Fortran 编译指示/指令中使用 "CDEC$ LOOP COUNT (20)",以便为编译器提供帮助。

此外还需考虑编译器执行的向量循环展开操作通过查看 -vec-report6 选项的输出结果。例如,如果编译器对一个循环(trip-count nvec-length 16)进行向量化处理并展开两倍(向量化以后),那么每个内核循环都要执行原始 src-loop 32 次迭代。如果动态 trip-count 正好是 20,内核循环会完全跳过,剩余循环负责所有执行任务。如果您遇到此问题,则可以使用 C/C++ 中的 "#pragma nounroll" Fortran 中的 "CDEC$ NOUNROLL" 来关闭向量循环的展开功能。(您还可以使用上面介绍的 loop_count 编译指示来影响编译器推断)。

如果您希望禁用编译器生成的剩余循环的向量化则可以在循环之前使用 C/C++ 中的 "#pragma vector novecremainder" Fortran 编译指示/指令中的 "CDEC$ vector noremainder"这也会禁用编译器针对该循环生成的剥离循环的向量化功能。您还可以使用编译器内部选项 -mP2OPT_hpo_vec_remainder=F 来禁用剩余循环向量化(针对编译范围内的所有循环)。如果您分析向量循环的汇编代码并且希望从行数明确识别向量内核循环,上述方法通常十分有用(否则,您必须在汇编中仔细筛选循环的多个版本 内核/剩余/剥离,以发现自己的目标)。

剥离循环

编译器通常会生成动态剥离循环以便对齐循环中的一个内存访问。该剥离循环会剥离原始 src-loop 的多个迭代,直到候选内存访问对齐。剥离循环确保有一个小于 vector-length trip-count。该优化的目的是确保内核向量循环能够使用更多的对齐加载/存储指令 从而提升内核循环的性能和效率。但是剥离循环本身(即便可被编译器向量化)的效率较低(查看编译器的 -vec-report6 输出结果)。解决此问题的最好方法是通过以下这种方式重构算法/代码,即对齐访问,而且编译器知道采用向量化对齐的常用方法(vectorizer alignment)进行对齐。如果编译器知道所有访问已经对齐(假设用户在循环之前正确使用"#pragma vector aligned",编译器能够安全地假定循环中的所有内存访问已经对齐),那么编译器便不会生成剥离循环。

此外您还可以使用上面介绍的 loop_count 编译指示来影响编译器是否创建剥离循环的决定。

你可以在 C/C++ 中添加 "#pragma vector unaligned" 或者在 Fortran 编译指示/指令中添加"CDEC$ vector unaligned"以便在源代码中的循环之前命令编译器不要生成动态剥离循环。

您可以结合使用向量编译指示/指令与上面提到的 novecremainder 语句以便禁用编译器生成的剥离循环的向量化功能。您还可以使用编译器内部选项 -mP2OPT_hpo_vec_peel=F 来禁用剩余循环向量化(针对编译范围内的所有循环)。

示例

% cat -n t2.c

           1  #include <stdio.h>

           2

           3  void foo1(float *a, float *b, float *c, int n)

           4  {

           5    int i;

           6  #pragma ivdep

           7    for (i=0; i<n; i++) {

           8      a[i] *= b[i] + c[i];

           9    }

          10  }

          11

          12  void foo2(float *a, float *b, float *c, int n)

          13  {

          14    int i;

          15  #pragma ivdep

          16    for (i=0; i<20; i++) {

          17      a[i] *= b[i] - c[i];

          18    }

          19  }

          20

对于第 7 行的循环编译器生成一个内核向量循环向量化以后展开两倍、一个剥离循环和剩余循环后面两个循环都进行向量化处理。

对于第 16 行的循环编译器利用 trip-count 是一个常量 (20) 的事实并生成经过向量化处理的内核循环未展开 剩余循环4 次迭代被编译器完全展开未向量化。未生成剥离循环。

% icc -O2 -vec-report6 t2.c -c -mmic -inline-level=0
t2.c(8):
(col. 5) remark: vectorization support: reference a has aligned access.
t2.c(8): (col. 5) remark: vectorization support: reference a has aligned access.
t2.c(8): (col. 5) remark: vectorization support: reference b has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference c has aligned access.
t2.c(8): (col. 5) remark: vectorization support: unaligned access used inside loop body.
t2.c(7): (col. 3) remark: vectorization support: unroll factor set to 2.
t2.c(7): (col. 3) remark: LOOP WAS VECTORIZED.
t2.c(8): (col. 5) remark: vectorization support: reference a has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference a has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference b has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference c has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: unaligned access used inside loop body.
t2.c(7): (col. 3) remark: PEEL LOOP WAS VECTORIZED.
t2.c(8): (col. 5) remark: vectorization support: reference a has aligned access.
t2.c(8): (col. 5) remark: vectorization support: reference a has aligned access.
t2.c(8): (col. 5) remark: vectorization support: reference b has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference c has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference a has aligned access.
t2.c(8): (col. 5) remark: vectorization support: reference a has aligned access.
t2.c(8): (col. 5) remark: vectorization support: reference b has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: reference c has unaligned access.
t2.c(8): (col. 5) remark: vectorization support: unaligned access used inside loop body.
t2.c(7): (col. 3) remark: REMAINDER LOOP WAS VECTORIZED.
t2.c(17): (col. 5) remark: vectorization support: reference a has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: reference a has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: reference b has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: reference c has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: unaligned access used inside loop body.
t2.c(16): (col. 3) remark: LOOP WAS VECTORIZED.
t2.c(17): (col. 5) remark: vectorization support: reference a has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: reference a has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: reference b has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: reference c has unaligned access.
t2.c(17): (col. 5) remark: vectorization support: unaligned access used inside loop body.
t2.c(16): (col. 3) remark: loop was not vectorized: vectorization possible but seems inefficient.

增加数组的大小并使用选项 -opt-assume-safe-padding 来提高性能

该选项决定了编译器是否假设该变量以及动态分配的内存被填充到对象的末端。

当指定 -opt-assume-safe-padding 编译器假定填充变量以及动态分配的内存。也就是说,该代码可以访问高达 64 字节(超出程序中所指定的)。使用该选项时,编译器不会针对静态和自动对象添加任何填充,但是它可以假定该代码能够访问对象末尾处的高达 64 字节,而无论该对象位于程序中的何处。为满足该假定条件,在使用该选项时,您必须增加程序中静态和自动对象的大小。

1. 此选项可在编译器针对向量剩余循环和向量剥离循环生成的序列中提供帮助。该选项可提高此类循环中内存操作的性能。

如果在上述编译中使用该选项编译器将会假定数组 ab c 至少具有 64 字节超过 n的填充。

如果使用 malloc 来分配数组例如

ptr = (float *)malloc(sizeof(float) * n);

那么用户应对其进行如下更改

ptr = (float *)malloc(sizeof(float) * n + 64);

完成更改后满足使用该选项的合法性要求),如果您向上述编译添加该选项您将在第 7 行处得到针对循环生成的剥离循环的下列高性能序列

..B2.7:                         # Preds ..B2.9 ..B2.6 Latency 13

        vpcmpgtd  %zmm0, %zmm2, %k0                             #7.3 c1

        nop                                                     #7.3 c5

        knot      %k0, %k1                                      #7.3 c9

        jkzd      ..B2.9, %k1   # Prob 20%                      #7.3 c13

                                # LOE rdx rbx rbp rsi rdi r9 r10 r11 r12 r13 r14

 r15 eax ecx r8d zmm0 zmm1 zmm2 zmm3 k1

..B2.8:                         # Preds ..B2.7 Latency 53

        vmovaps   %zmm1, %zmm4                                  #8.13 c1

        vmovaps   %zmm1, %zmm5                                  #8.20 c5

        vmovaps   %zmm1, %zmm6                                  #8.5 c9

        vloadunpacklps (%rsi,%r10,4), %zmm4{%k1}                #8.13 c13

        vloadunpacklps (%rdx,%r10,4), %zmm5{%k1}                #8.20 c17

        vloadunpacklps (%rdi,%r10,4), %zmm6{%k1}                #8.5 c21

        vloadunpackhps 64(%rsi,%r10,4), %zmm4{%k1}              #8.13 c25

        vloadunpackhps 64(%rdx,%r10,4), %zmm5{%k1}              #8.20 c29

        vloadunpackhps 64(%rdi,%r10,4), %zmm6{%k1}              #8.5 c33

        vaddps    %zmm5, %zmm4, %zmm7                           #8.20 c37

        vmulps    %zmm7, %zmm6, %zmm8                           #8.5 c41

        nop                                                     #8.5 c45

        vpackstorelps %zmm8, (%rdi,%r10,4){%k1}                 #8.5 c49

        vpackstorehps %zmm8, 64(%rdi,%r10,4){%k1}               #8.5 c53

        movb      %al, %al                                      #8.5 c53

                                # LOE rdx rbx rbp rsi rdi r9 r10 r11 r12 r13 r14

 r15 eax ecx r8d zmm0 zmm1 zmm2 zmm3

..B2.9:                         # Preds ..B2.7 ..B2.8 Latency 9

        addq      $16, %r10                                     #7.3 c1

        vpaddd    %zmm3, %zmm2, %zmm2                           #7.3 c5

        cmpq      %r11, %r10                                    #7.3 c5

        jb        ..B2.7        # Prob 82%                      #7.3 c9

如果不使用该选项编译器会使用收集/分散在第 7 行处生成剥离循环的低性能序列。

..B2.7:                         # Preds ..B2.9 ..B2.6 Latency 13

        vpcmpgtd  %zmm0, %zmm2, %k0                             #7.3 c1

        nop                                                     #7.3 c5

        knot      %k0, %k4                                      #7.3 c9

        jkzd      ..B2.9, %k4   # Prob 20%                      #7.3 c13

                                # LOE rax rdx rbx rbp rsi rdi r9 r11 r13 r15 ecx

 r8d r10d zmm0 zmm1 zmm2 zmm3 k4

..B2.8:                         # Preds ..B2.7 Latency 57

        vmovaps   .L_2il0floatpacket.10(%rip), %zmm8            #8.5 c1

        vmovaps   %zmm1, %zmm4                                  #8.13 c5

        lea       (%rsi,%r13), %r14                             #8.13 c5

        vmovaps   %zmm1, %zmm5                                  #8.20 c9

        kmov      %k4, %k2                                      #8.13 c9

..L15:                                                          #8.13

        vgatherdps (%r14,%zmm8,4), %zmm4{%k2}                   #8.13

        jkzd      ..L14, %k2    # Prob 50%                      #8.13

        vgatherdps (%r14,%zmm8,4), %zmm4{%k2}                   #8.13

        jknzd     ..L15, %k2    # Prob 50%                      #8.13

..L14:                                                          #

        vmovaps   %zmm1, %zmm6                                  #8.5 c21

        kmov      %k4, %k3                                      #8.20 c21

        lea       (%rdx,%r13), %r14                             #8.20 c25

        lea       (%rdi,%r13), %r12                             #8.5 c25

..L17:                                                          #8.20

        vgatherdps (%r14,%zmm8,4), %zmm5{%k3}                   #8.20

        jkzd      ..L16, %k3    # Prob 50%                      #8.20

        vgatherdps (%r14,%zmm8,4), %zmm5{%k3}                   #8.20

        jknzd     ..L17, %k3    # Prob 50%                      #8.20

..L16:                                                          #

        vaddps    %zmm5, %zmm4, %zmm7                           #8.20 c37

        kmov      %k4, %k1                                      #8.5 c37

..L19:                                                          #8.5

        vgatherdps (%r12,%zmm8,4), %zmm6{%k1}                   #8.5

        jkzd      ..L18, %k1    # Prob 50%                      #8.5

        vgatherdps (%r12,%zmm8,4), %zmm6{%k1}                   #8.5

        jknzd     ..L19, %k1    # Prob 50%                      #8.5

..L18:                                                          #

        vmulps    %zmm7, %zmm6, %zmm9                           #8.5 c49

        nop                                                     #8.5 c53

..L21:                                                          #8.5

        vscatterdps %zmm9, (%r12,%zmm8,4){%k4}                  #8.5

        jkzd      ..L20, %k4    # Prob 50%                      #8.5

        vscatterdps %zmm9, (%r12,%zmm8,4){%k4}                  #8.5

        jknzd     ..L21, %k4    # Prob 50%                      #8.5

..L20:                                                          #

                                # LOE rax rdx rbx rbp rsi rdi r9 r11 r13 r15 ecx

 r8d r10d zmm0 zmm1 zmm2 zmm3

..B2.9:                         # Preds ..B2.7 ..B2.8 Latency 9

        addq      $16, %rax                                     #7.3 c1

        addq      $64, %r13                                     #7.3 c1

        vpaddd    %zmm3, %zmm2, %zmm2                           #7.3 c5

        cmpq      %r9, %rax                                     #7.3 c5

        jb        ..B2.7        # Prob 82%                      #7.3 c9

2. 另外一个关于使用该选项的示例是处理较短的整数类型转换。在这种情况下,可通过添加选项 -opt-assume-safe-padding 来改进编译器在默认选项下生成的代码。

void foo(short * restrict a, short *restrict b, short * restrict c)

{

   int i;

 

   for(i = 0; i < N; i++) {

       a[i] = b[i] + c[i];

   }

}

在主内核循环中编译器为每个加载/存储添加检查功能防止内存发生故障。在剩余循环中发出收集/分散指令:

采用默认选项的主内核循环:

..B1.6:

        lea       (%rax,%rsi), %r10

        vloadunpackld (%rax,%rsi){sint16}, %zmm1

        andq      $63, %r10

        cmpq      $32, %r10

        jle       ..L3

        vloadunpackhd 64(%rax,%rsi){sint16}, %zmm1

..L3:

        vprefetch1 256(%rax,%rsi)

        lea       (%rax,%rdx), %r10

        vloadunpackld (%rax,%rdx){sint16}, %zmm2

        andq      $63, %r10

        cmpq      $32, %r10

        jle       ..L4

        vloadunpackhd 64(%rax,%rdx){sint16}, %zmm2

..L4:

        vprefetch0 128(%rax,%rsi)

        vpaddd    %zmm2, %zmm1, %zmm3

        vprefetch1 256(%rax,%rdx)

        vpandd    %zmm0, %zmm3, %zmm4

        vprefetch0 128(%rax,%rdx)

        addq      $16, %rcx

        vprefetch1 256(%rax,%rdi)

        lea       (%rax,%rdi), %r10

        andq      $63, %r10

        cmpq      $32, %r10

        jle       ..L5

        vpackstorehd %zmm4{uint16}, 64(%rax,%rdi)

..L5:

        vpackstoreld %zmm4{uint16}, (%rax,%rdi)

        vprefetch0 128(%rax,%rdi)

        addq      $32, %rax

        cmpq      $992, %rcx

        jb        ..B1.6

 

编译器采用默认选项时生成的剩余循环:

..L9:

        vpgatherdd 1984(%rdx,%zmm3,2){sint16}, %zmm1{%k2}

        jkzd      ..L8, %k2

        vpgatherdd 1984(%rdx,%zmm3,2){sint16}, %zmm1{%k2}

        jknzd     ..L9, %k2

..L8:

        vpaddd    %zmm1, %zmm0, %zmm2

        vpandd    .L_2il0floatpacket.3(%rip), %zmm2, %zmm4

        nop

..L11:

        vpscatterdd %zmm4{uint16}, 1984(%rdi,%zmm3,2){%k3}

        jkzd      ..L10, %k3   

        vpscatterdd %zmm4{uint16}, 1984(%rdi,%zmm3,2){%k3}

        jknzd     ..L11, %k3   

..L10:

 

添加选项 -opt-assume-safe-padding 编译器为主内核循环和剩余循环生成下列更高性能的版本

采用 -opt-assume-safe-padding 选项的主内核循环

..B1.6:

        vloadunpackld (%rax,%rsi){sint16}, %zmm1

        vprefetch1 256(%rax,%rsi)

        vloadunpackld (%rax,%rdx){sint16}, %zmm2

        vprefetch0 128(%rax,%rsi)

        vloadunpackhd 64(%rax,%rsi){sint16}, %zmm1

        vprefetch1 256(%rax,%rdx)

        vloadunpackhd 64(%rax,%rdx){sint16}, %zmm2

        vprefetch0 128(%rax,%rdx)

        vpaddd    %zmm2, %zmm1, %zmm3

        vprefetch1 256(%rax,%rdi)

        vpandd    %zmm0, %zmm3, %zmm4

        vprefetch0 128(%rax,%rdi)

        addq      $16, %rcx

        movb      %dl, %dl

        vpackstoreld %zmm4{uint16}, (%rax,%rdi)

        vpackstorehd %zmm4{uint16}, 64(%rax,%rdi)

        addq      $32, %rax

        cmpq      $992, %rcx

        jb        ..B1.6

添加  -opt-assume-safe-padding 选项时的剩余循环不采用收集/分散的高性能版本):

        vloadunpackld 1984(%rsi){sint16}, %zmm0{%k1}

        vloadunpackld 1984(%rdx){sint16}, %zmm1{%k1}

        vloadunpackhd 2048(%rsi){sint16}, %zmm0{%k1}

        vloadunpackhd 2048(%rdx){sint16}, %zmm1{%k1}

        vpaddd    %zmm1, %zmm0, %zmm2

        vpandd    .L_2il0floatpacket.3(%rip), %zmm2, %zmm3

        nop

        vpackstoreld %zmm3{uint16}, 1984(%rdi){%k1}

        vpackstorehd %zmm3{uint16}, 2048(%rdi){%k1}

        movb      %al, %al

下一步

要在英特尔® 至强 融核架构上成功调试您的应用请务必通读此指南并点击文中的超链接查看相关内容。本指南提供了实现最佳应用性能所要执行的步骤。

返回到主章节“向量化要素? HYPERLINK "http://software.intel.com/en-us/articles/vectorization-essential" 矢量化要素??

 

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.