fortran矢量长度和fortran循环的展开因子

Shi*_*iyu 4 fortran simd vectorization loop-unrolling

我想用SIMD指令对下面的fortran进行矢量化

!DIR$ SIMD
    DO IELEM = 1 , NELEM
      X(IKLE(IELEM)) = X(IKLE(IELEM)) + W(IELEM)
    ENDDO
Run Code Online (Sandbox Code Playgroud)

我使用了avx2指令.该程序由编译

ifort main_vec.f -simd -g -pg -O2 -vec-report6 -o vec.out -xcore-avx2 -align array32byte
Run Code Online (Sandbox Code Playgroud)

然后我想VECTORLENGTH(n)在之后添加条款SIMD.如果没有这样的条款或n = 2,4,则该信息不提供有关展开因子的信息

如果n = 8,16 , vectorization support: unroll factor set to 2.

我已经阅读了英特尔关于矢量化支持的文章:展开因子设置为xxxx所以我想循环展开到类似:

    DO IELEM = 1 , NELEM, 2
      X(IKLE(IELEM)) = X(IKLE(IELEM)) + W(IELEM)
      X(IKLE(IELEM+1)) = X(IKLE(IELEM+1)) + W(IELEM+1)
    ENDDO
Run Code Online (Sandbox Code Playgroud)

然后2 X进入向量寄存器,2 W进入另一个,进行加法.但VECTORLENGTH的价值如何运作?或许我真的不明白矢量长度是什么意思.

因为我使用avx2指令,对于DOUBLE PRECISION类型X,最大长度是多少?

这是循环组装的一部分,SSE2,VL = 8,编译器告诉我,展开因子是2.但是它使用了4个寄存器而不是2个寄存器.

.loc    1  114  is_stmt 1
        movslq    main_vec_$IKLE.0.1(,%rdx,4), %rsi             #114.9
..LN202:
        movslq    4+main_vec_$IKLE.0.1(,%rdx,4), %rdi           #114.9
..LN203:
        movslq    8+main_vec_$IKLE.0.1(,%rdx,4), %r8            #114.9
..LN204:
        movslq    12+main_vec_$IKLE.0.1(,%rdx,4), %r9           #114.9
..LN205:
        movsd     -8+main_vec_$X.0.1(,%rsi,8), %xmm0            #114.26
..LN206:
        movslq    16+main_vec_$IKLE.0.1(,%rdx,4), %r10          #114.9
..LN207:
        movhpd    -8+main_vec_$X.0.1(,%rdi,8), %xmm0            #114.26
..LN208:
        movslq    20+main_vec_$IKLE.0.1(,%rdx,4), %r11          #114.9
..LN209:
        movsd     -8+main_vec_$X.0.1(,%r8,8), %xmm1             #114.26
..LN210:
        movslq    24+main_vec_$IKLE.0.1(,%rdx,4), %r14          #114.9
..LN211:
        addpd     main_vec_$W.0.1(,%rdx,8), %xmm0               #114.9
..LN212:
        movhpd    -8+main_vec_$X.0.1(,%r9,8), %xmm1             #114.26
..LN213:
..LN214:
        movslq    28+main_vec_$IKLE.0.1(,%rdx,4), %r15          #114.9
..LN215:
        movsd     -8+main_vec_$X.0.1(,%r10,8), %xmm2            #114.26
..LN216:
        addpd     16+main_vec_$W.0.1(,%rdx,8), %xmm1            #114.9
..LN217:
        movhpd    -8+main_vec_$X.0.1(,%r11,8), %xmm2            #114.26
..LN218:
..LN219:
        movsd     -8+main_vec_$X.0.1(,%r14,8), %xmm3            #114.26
..LN220:
        addpd     32+main_vec_$W.0.1(,%rdx,8), %xmm2            #114.9
..LN221:
        movhpd    -8+main_vec_$X.0.1(,%r15,8), %xmm3            #114.26
..LN222:
..LN223:
        addpd     48+main_vec_$W.0.1(,%rdx,8), %xmm3            #114.9
..LN224:
        movsd     %xmm0, -8+main_vec_$X.0.1(,%rsi,8)            #114.9
..LN225:
   .loc    1  113  is_stmt 1
        addq      $8, %rdx                                      #113.7
..LN226:
   .loc    1  114  is_stmt 1
        psrldq    $8, %xmm0                                     #114.9
..LN227:
   .loc    1  113  is_stmt 1
        cmpq      $26000, %rdx                                  #113.7
..LN228:
   .loc    1  114  is_stmt 1
        movsd     %xmm0, -8+main_vec_$X.0.1(,%rdi,8)            #114.9
..LN229:
        movsd     %xmm1, -8+main_vec_$X.0.1(,%r8,8)             #114.9
..LN230:
        psrldq    $8, %xmm1                                     #114.9
..LN231:
        movsd     %xmm1, -8+main_vec_$X.0.1(,%r9,8)             #114.9
..LN232:
        movsd     %xmm2, -8+main_vec_$X.0.1(,%r10,8)            #114.9
..LN233:
        psrldq    $8, %xmm2                                     #114.9
..LN234:
        movsd     %xmm2, -8+main_vec_$X.0.1(,%r11,8)            #114.9
..LN235:
        movsd     %xmm3, -8+main_vec_$X.0.1(,%r14,8)            #114.9
..LN236:
        psrldq    $8, %xmm3                                     #114.9
..LN237:
        movsd     %xmm3, -8+main_vec_$X.0.1(,%r15,8)            #114.9
..LN238:
Run Code Online (Sandbox Code Playgroud)

zam*_*zam 6

1)向量长度 N是在"矢量化"循环之后可以并行执行的元素/迭代的数量(通常通过将数组X的N个元素放入单个向量寄存器并通过向量指令完全处理它们).为简化起见,将Vector Length视为此公式给出的值:

Vector Length (abbreviated VL) = Vector Register Width / Sizeof (data type)
Run Code Online (Sandbox Code Playgroud)

对于AVX2,矢量寄存器宽度= 256位.Sizeof(双精度)= 8字节= 64位.从而:

Vector Length (double FP, avx2) = 256 / 64 = 4
Run Code Online (Sandbox Code Playgroud)

$ DIR SIMD VECTORLENGTH(N)基本上强制编译器使用指定的向量长度(并将数组X的N个元素放入单个向量寄存器).而已.

2)展开和矢量化关系.为简化起见,将展开和矢量化视为通常不相关(稍微"正交")的优化技术.

如果你的循环以M因子展开(M可能是2,4,...),那么它并不一定意味着完全使用了向量寄存器,并不意味着你的循环在任何意义上都是并行化的.这意味着原始循环迭代的M个实例已被组合成单个迭代; 并且在给定的新"unwinded"/"unrolled"迭代中,旧的ex-iterations是逐个执行的(所以你的猜测示例绝对正确).

展开的目的通常是使循环更"微架构/内存友好".更详细地说:通过使循环迭代更加"胖",您通常可以改善CPU资源压力与内存/缓存资源压力之间的平衡,尤其是在展开之后,您可以更有效地重用寄存器中的某些数据.

3)展开+矢量化.编译器同时矢量化(使用VL = N)并展开(通过M)某些循环并不罕见.因此,优化循环中的迭代次数小于原始循环中的迭代次数大约NxM因子,但并行处理的元素数量(在给定时刻同时)将仅为N.因此,在您的示例中,如果循环使用VL = 4进行矢量化,并且展开2,那么它的伪代码可能如下所示:

DO IELEM = 1 , NELEM, 8
  [X(IKLE(IELEM)),X(IKLE(IELEM+2)), X(IKLE(IELEM+4)), X(IKLE(IELEM+6))] = ...
  [X(IKLE(IELEM+1)),X(IKLE(IELEM+3)), X(IKLE(IELEM+5)), X(IKLE(IELEM+7))] = ...
ENDDO
Run Code Online (Sandbox Code Playgroud)

其中方括号"对应"向量寄存器内容.

4)针对展开的矢量化:

  • 对于迭代次数相对较少的循环(特别是在C++中) - 可能会出现不需要展开的问题,因为它部分阻止了有效的向量化(没有足够的迭代来并行执行)和(从我的人工示例中看到)可能会以某种方式影响数据必须从内存加载的方式.不同的编译器具有不同的启发式方法,可以平衡Trip Counts,VL和Unrolling之间的相互关系; 这可能是因为当VL小于8时,在你的情况下禁用unroll.
  • 可以使用" 英特尔(矢量化)顾问 " 来探索行程计数,展开计数和向量长度之间的运行时和编译时权衡以及适当的自动建议(特别是在使用新的英特尔C++或Fortran编译器的情况下): 在此输入图像描述

5)PS还有第三个维度(我真的不想谈论它).

当用户请求的矢量长度大于给定硬件上可能的矢量长度时(假设指定avx2平台的矢量长度(16)为双FP)或者当你混合使用不同的类型时,编译器可以(或不能)开始使用"虚拟向量寄存器"并开始进行双/四泵浦.M-pumping是一种展开方式,但仅适用于单指令(即泵送导致重复单指令,而展开则导致重复整个循环体).您可以尝试像鉴于最近OpenMP的书籍阅读有关M-抽一个.因此,在某些情况下,你可能最终会叠加a)矢量化,b)展开和c)双泵浦,但这不常见,我会避免强制执行vectorlength> 2*ISA_VectorLength.