【问题标题】:simd vectorlength and unroll factor for fortran loopfortran循环的simd向量长度和展开因子
【发布时间】:2015-08-18 08:53:34
【问题描述】:

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

!DIR$ SIMD
    DO IELEM = 1 , NELEM
      X(IKLE(IELEM)) = X(IKLE(IELEM)) + W(IELEM)
    ENDDO

我使用了 avx2 指令。该程序由

编译
ifort main_vec.f -simd -g -pg -O2 -vec-report6 -o vec.out -xcore-avx2 -align array32byte

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

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

我已经阅读了英特尔关于 vectorization support: unroll factor set to 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

然后 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:

【问题讨论】:

    标签: fortran vectorization simd loop-unrolling


    【解决方案1】:

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

    Vector Length (abbreviated VL) = Vector Register Width / Sizeof (data type)
    

    对于 AVX2 ,向量寄存器宽度 = 256 位。 Sizeof(双精度)= 8 字节 = 64 位。因此:

    Vector Length (double FP, avx2) = 256 / 64 = 4
    

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

    2) 展开和矢量化关系。为简化起见,将展开和矢量化视为通常不相关(有点“正交”)的优化技术。

    如果您的循环以 M 因子展开(M 可能是 2、4、..),那么这并不一定意味着使用了向量寄存器,它确实意味着你的循环在任何意义上都是并行的。相反,它的意思是将原始循环迭代的 M 个实例组合到一个迭代中;并在给定的新“展开”/“展开”迭代中,旧的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
    

    ,其中方括号“对应”向量寄存器内容。

    4) 针对展开的矢量化

    • for 循环的迭代次数相对较少(尤其是在 C++ 中) - 展开可能是不可取的,因为它会部分阻止有效的矢量化(没有足够的迭代来并行执行)和(如您从我的人工示例中看到的)可能会以某种方式影响必须从内存加载数据的方式。不同的编译器在平衡 Trip Counts、VL 和 Unrolling 之间具有不同的启发式方法;这可能就是为什么当 VL 小于 8 时 unroll 被禁用的原因。
    • 可以使用“Intel (Vectorization) Advisor”探索行程计数、展开和向量长度以及适当的自动建议(尤其是在使用新的英特尔 C++ 或 Fortran 编译器的情况下)之间的运行时和编译时权衡:

    5)还有第三个维度(我不太喜欢谈论它)。

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

    【讨论】:

    • 我直接与英特尔文章的作者交谈,他们确认他们必须修复它
    • (他们没有说他们必须修复编译器,问题仅与诊断和文章有关;编译器像我解释的那样工作;如果您不同意,请制作一个测试用例并查看汇编,您将找到确认)
    • 好吧,我不是 (x86) 汇编专家(我更喜欢从 gfortran 看 GIMPLE),但确实有 4 条 VADDPD 指令连续出现。 +1
    • 感谢您的解释,非常清楚。但是我还是想知道,对于双精度变量,如果VL > 4,那么我们连avx都不能只用一个寄存器,它是如何工作的?
    • 好的,我在回答中添加了项目符号 (5) 只是为了解决您的理论问题。老实说,我不想接触多泵,因为通常你不会经常看到它,因为指定超大向量长度更像是理论练习,几乎没有实际适用性(尽管对于一些混合了 short、int、 float 和 double,你可能最终还是会这样做)
    猜你喜欢
    • 1970-01-01
    • 2017-12-16
    • 2012-08-31
    • 2014-05-12
    • 2021-10-08
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多