65.9K
CodeProject 正在变化。 阅读更多。
Home

利用 Intel® AVX-512 进行矢量化以提高性能的机会

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2017年4月17日

CPOL

8分钟阅读

viewsIcon

21961

Intel® 编译器如何矢量化和加速循环的示例

点击此处注册并下载您免费的 30 天 Intel® Parallel Studio XE 试用版。

Martyn Corden, Intel 公司软件技术咨询工程师

曾经靠不断提高时钟速度就能轻松获得性能提升的日子早已一去不复返。摩尔定律转而通过增加更多核心以及更宽的 SIMD 寄存器来实现更高的并行度。除了将 SIMD 向量宽度增加到 512 位之外,Intel® Advanced Vector Extensions 512 (Intel® AVX-512) 还包含一些新指令,这些指令能够向量化一些以前无法用旧指令集向量化的循环,并能更有效地向量化其他循环。

在本文中,我们将通过示例解释 Intel® C/C++ 和 Fortran 编译器如何向量化和加速压缩或扩展数组的循环,以及填充直方图或执行可能存在地址冲突的散列操作的循环。我们还将展示编译器如何将步进式加载或收集转换为更高效的单位步进 SIMD 加载,用于处理结构数组的某些类型循环。最后,我们将展示如何使用 Intel® Parallel Studio XE 2017 中 Intel® 编译器版本 17.0 的新优化报告功能来识别此转换<LINK TO https://software.intel.com/en-us/intel-parallel-studio-xe >。这些优化不仅可以惠及运行在 Intel® Xeon Phi™ X200 处理器上的广泛应用,还可以惠及未来支持 Intel AVX-512 指令集的 Intel® Xeon® 处理器。

向量化复习

图 1 展示了一个简单的双精度浮点循环。在标量模式下,一条指令产生一个结果。向量化后,一条 Intel AVX-512 指令可以产生八个结果,而 Intel® AVX 产生四个,Intel® Streaming SIMD Extensions (Intel® SSE) 产生两个。Intel Xeon Phi x200 处理器支持 Intel AVX-512 指令,可对 32 位和 64 位整数及浮点数据执行各种操作。未来 Intel Xeon 处理器上可能会扩展到 8 位和 16 位整数。

Intel® 编译器默认启用自动向量化。但是,必须设置图 2 中的某个开关才能以 Intel AVX-512 指令集为目标。

Linux* 和 OS X*

Windows*

功能

-xmic-avx512

/Qxmic-avx2

仅限 Intel® Xeon Phi™ x200 处理器系列

-xcore-avx512

/Qxcore-avx512

仅限未来 Intel® Xeon® 处理器

-xcommon-avx512

/Qxcommon-avx512

Intel® AVX-512 在两者之间共用的子集。

不是一个胖二进制文件。

-axmic-avx512

/Qaxmic-avx512

胖二进制文件。以 Intel® Xeon Phi™ x200

以及其他 Intel® Xeon® 处理器为目标

-xhost

/Qxhost

以编译主机为目标

表 1. 启用 Intel® AVX-512 指令的编译器开关

压缩和扩展循环

图 2A 中的 Fortran 示例演示了数组压缩。只有满足条件的较大源数组中的元素才会被复制到较小的目标数组中。图 2B 中的 C 示例演示了逆向操作(即数组扩展),其中较小的源数组中的元素被复制回较大的稀疏数组中。

   nb = 0
   do ia=1, na               ! line 23
     if (a(ia) > 0.) then
       nb = nb + 1          ! dependency
       b(nb) = a(ia)         ! compress
     endif
   enddo
图 2A. 数组压缩
int j = 0
for (int i=0; i <N; i++) {
    if (a[i] > 0) {
        c[i] = a[k++];   // expand
    }
}
// Cross-iteration dependencies via j and k
图 2B. 数组扩展

密集数组索引的条件增量引入了循环迭代之间的依赖关系。过去,这会阻止自动向量化。例如,在编译图 2A 中的循环并以 Intel® AVX2 为目标时,优化报告显示

ifort -c -xcore-avx2 -qopt-report-file=stderr -qopt-report=3 compress.f90
…
LOOP BEGIN at compress.f90(23,3)
  remark #15344: loop was not vectorized: vector dependence prevents vectorization.  …                              
  remark #15346: vector dependence: assumed ANTI dependence between nb (25:7) and nb (25:7)
LOOP END

图 2B 中的 C 示例行为相同。也不能使用 OpenMP* SIMD 指令,因为依赖关系会导致结果不正确。

Intel AVX-512 通过新的 vcompress 指令克服了这种依赖关系,该指令将一个 SIMD 寄存器中选定的元素写入另一个寄存器或内存中的连续元素。同样,vexpand 指令将源寄存器或内存中的连续元素写入目标 SIMD 寄存器中选定的(稀疏)元素。这些新指令允许编译器在启用 Intel AVX-512 时向量化压缩示例(图 2A)(图 3)。

ifort -c -xmic-avx512 -qopt-report-file=stderr -qopt-report=3 compress.f90
…
LOOP BEGIN at compress.f90(23,3)
   remark #15300: LOOP WAS VECTORIZED
   remark #15450: unmasked unaligned unit stride loads: 1
   remark #15457: masked unaligned unit stride stores: 1
…
   remark #15497: vector compress: 1
LOOP END
图 3. 启用 Intel AVX-512 时的向量化

加载了源数组的所有元素,因此加载是未屏蔽的。仅存储选定的元素,因此有效存储是屏蔽的。优化报告包含 remark #15497,显示识别并向量化了压缩模式。使用 -S 选项获得的汇编列表显示了类似以下的指令

vcompressps %zmm4, -4(%rsi,%rdx,4){%k1}

对于图 2B 中的数组扩展循环,也会获得类似的结果。

在 Intel Xeon Phi 7250 处理器上,将包含 1,000,000 个随机值的单精度数组按两倍的比例压缩,重复 1,000 次,与 Intel AVX2 相比,Intel AVX-512 的速度提升了约 16 倍,这与 SIMD 寄存器和指令的宽度相匹配。

Intel AVX-512 冲突检测指令

包含带间接寻址存储的循环存在潜在依赖关系,通常会阻止向量化。例如

for (i=0; i<n; i++)  a[index[i]] = …

如果对于不同的 i 值,index[i] 具有相同的值,则存储会发生冲突,无法安全地同时执行。Intel AVX-512 中的 vpconflict 指令通过为无冲突的 SIMD 通道(i 的值)(即 `index[i]` 的值没有重复)提供一个掩码来解决此冲突。在这些通道安全地执行了 SIMD 计算之后,将重新执行被掩掉通道的循环。

直方图填充

填充直方图是许多应用程序中的常见操作(例如,图像处理)。图 4 中的代码片段为输入值数组 x 在数组 h 中填充 sin(x) 的直方图。使用 Intel AVX2,由于依赖关系,这无法向量化,因为两个输入值可能贡献到同一个直方图 bin,ih

  for (i=0; i<n; i++) {                   
      y     = sinf(x[i]*twopi);
      ih    = floor((y-bot)*invbinw);
      ih    = ih > 0     ? ih : 0;
      ih    = ih < nbin-1 ? ih : nbin-1;
      h[ih] = h[ih] + 1;                       //  line 25
   }
图 4. 填充 sin(x) 直方图的循环
icc -c -xcore-avx2 histo.c -qopt-report-file=stderr -qopt-report-phase=vec
…
LOOP BEGIN at histo2.c(20,4)
      remark #15344: loop was not vectorized: vector dependence prevents vectorization…
      remark #15346: vector dependence: assumed FLOW dependence between h[ih] (25:7) and h[ih] (25:7)
LOOP END
图5。

Intel AVX-512 冲突检测指令允许安全地向量化此循环

ifort -c -xmic-avx512 histo2.f90 -qopt-report-file=stderr -qopt-report=3 -S
…
LOOP BEGIN at histo2.c(20,4)
   remark #15300: LOOP WAS VECTORIZED
   remark #15458: masked indexed (or gather) loads: 1 
   remark #15459: masked indexed (or scatter) stores: 1
   remark #15499: histogram: 2
LOOP END
图6。

在汇编代码(未显示)中,加载 xindex,并为所有 SIMD 通道计算 ih。接下来是 vpconflict 指令,以及将唯一的 bin(即 h 的元素)收集到一个寄存器中,然后对其进行增量操作。如果 ih 的某些值重复,代码将回退并重新增量相应的 bin。最后,将增量后的 bin 屏蔽存储(散列)回 h 中。

一项简单的测试,从 0 到 1 之间的 100,000,000 个随机值的单精度数组中填充 200 个 bin 的直方图,重复 10 次,首先针对 Intel AVX2 编译,然后针对 Intel AVX-512 编译,并在 Intel Xeon Phi 7250 处理器上运行。观察到了近 9 倍的速度提升。速度提升很大程度上取决于问题的细节。它主要来自循环计算(例如此示例中的正弦函数)的向量化,而不是来自收集和散列本身。在冲突相对不频繁的常见情况下,速度提升可能很大。但是,如果存在许多冲突,例如直方图中存在奇异点或窄峰,速度提升可能会较小。

收集到混洗的优化

包含大量小结构或短向量的大型数组很常见,但对高效向量化提出了挑战。由于 trip count 低,对结构内容或短向量进行向量化可能不可能或效率低下。对大型数组索引进行向量化效率也很低,因为连续迭代使用的数据在内存中不相邻,因此无法使用高效的 SIMD 加载连续数据。图 7 说明了这一点,它只是计算了一个 3 向量数组组件的平方和。

struct Point { float x; float y; float z; };

float sumsq( struct Point *ptvec, int n) {
    float   t_sum = 0;

    for (int i=0; i<n; i++) {
        t_sum += ptvec[i].x * ptvec[i].x;
        t_sum += ptvec[i].y * ptvec[i].y;
        t_sum += ptvec[i].z * ptvec[i].z;
    }
    return  t_sum;
}
图7。

旧版 15 编译器使用步进加载或收集来加载结构体的每个组件来向量化此循环

icc -std=c99 -xmic-avx512 -qopt-report=4 -qopt-report-file=stderr -qopt-report-phase=vec,cg
…
LOOP BEGIN at g2s.c(6,5)
   remark #15415: vectorization support: gather was generated for the variable ptvec:  strided by 3   
…
   remark #15300: LOOP WAS VECTORIZED
   remark #15460: masked strided loads: 6
图8。

然而,编译器可以通过注意到结构体的 x、yz 组件在内存中是相邻的,从而做得更好。编译器可以执行组件的 SIMD 加载,然后执行置换和混洗以转置数据,使其布局适合对 i 循环进行向量化,如图 9 所示。

图 9. 数据转置以实现向量化

使用较新的 17 版本编译器编译时,优化报告包含一个“代码生成”组件

Report from: Code generation optimizations [cg]
sumsq.c(10,22):remark #34030: adjacent sparse (strided) loads optimized for speed. Details: stride { 12 }, types { F32-V512, F32-V512, F32-V512 }, number of elements { 16 }, select mask { 0x000000007 }.
图10。

这表明编译器能够将原始的步进加载转换为连续的 SIMD 加载,然后进行混洗和置换。一项对 100,000 次重复的 10,000 个随机点的简单测试循环,在 Intel Xeon Phi 7250 处理器上使用 Intel AVX-512 编译并运行。与 15 版本编译器相比,17 版本编译器的速度提升超过 2 倍。如果 pt 是二维数组 pt[10000][3],或者在 Fortran 中,如果 pt 是二维数组 pt(3,10000) 或派生类型数组,则会执行相同的优化。

编译器有时可以在以其他指令集为目标时执行此“收集到混洗”优化。然而,强大新颖的混洗和置换指令使得在以 Intel AVX-512 为目标时,其生成频率要高得多。

摘要

Intel AVX-512 中的强大新指令通过向量化一些以前无法向量化的循环,以及更有效地向量化其他循环,从而实现了应用程序性能的提升。编译器优化报告显示了这些优化的执行时间和位置。

额外资源

最初为 Intel® Xeon Phi™ x100 协处理器编写,也适用于其他地方

网络研讨会

© . All rights reserved.