简介

本文主要介绍编译器向量化的主题。 向量化是数据并行编程的一种形式,其中处理器对一个向量的 n 个数据元素(一维标量数据对象数组,如浮点对象、整数或双精度浮点对象)同时执行相同的操作。

向量化的技能和知识对于提高 Intel 至强产品系列的性能至关重要。在完美的情况下,应用程序的矢量化可以给出高达8倍(双精度)或16倍(单精度浮点数)的加速比。您的应用程序可能无法达到这些潜在的加速,但是需要明确的是,如果您的代码没有向量化,那么它将不会使用 Intel Xeon 产品系列中所有可用的计算特性。

作为第一步,必须理解向量化——它是什么以及如何使用向量化报告来确定编译器能够在哪里向量化您的应用程序。使用 -qopt-report 生成向量化报告。了解编译器不能向量化代码的哪些部分以及为什么不能向量化代码也很重要。

由于期望编译器完成向量化的所有工作是不现实的,因此需要了解编译器的准则和指令,以帮助编译器进行向量化。一个关键技术,以援助有效的矢量化是数据对齐。本章描述如何控制数据对齐并帮助编译器识别对齐的数据。

向量化技术

基本要求

循环的向量化对于具有 Intel AVX 特性的 Intel c + + 和 Fortran 架构的 Intel c + + 和 Fortran 编译器来说,循环的“向量化”意味着展开循环,这样它就可以利用 Intel AVX 提供的打包 SIMD 指令,对单个指令中的多个数据元素执行相同的操作。例如,其中非向量化的 DAXPY 循环

for (i=0;i<MAX;i++) z[i]=a*x[i]+y[i]; 

可能使用标量 SIMD 指令,如 addsd 和 mulsd,向量化循环将使用打包版本、 addpd 或 mulpd。(在倒数第二个字符中,s 代表“ scalar” ,p 代表“ packed”。在最后一个字符中,s 代表单精度,d 代表双精度)。在最新的 Intel 编译器中,向量化是默认启用的许多优化之一。向量化可以认为是同时执行原始循环的多个连续迭代。对于支持 SSE 的处理器,这通常是2或4次迭代,但是可能更多,特别是对于整数算术或更高级的指令集。这导致了对可向量化的循环类型的一些限制。有效的向量化的其他要求来自 SIMD 指令本身的特性。

循环向量化的要求:

  • 循环应该主要由直线代码组成。不应该有跳转或分支,比如 switch 语句,但是允许使用屏蔽赋值,包括可以解释为屏蔽赋值的 if-then-else 构造。
  • 循环应该是可数的,即迭代次数应该在循环开始执行之前就知道,尽管在编译时不需要知道。因此,除了非常简单的搜索循环之外,应该不存在依赖于数据的退出条件。
  • 不应该存在反向循环执行的依赖关系。例如,循环不能要求迭代1的语句2在迭代2的语句1之前执行以获得正确的结果。这允许在展开的矢量化循环的单次迭代中同时执行原循环的连续迭代。OK (vectorizable) : a [ i-1]总是在使用之前计算:
    for (i=1; i<MAX; i++) {
     a[i] = b[i] + c[i]
     d[i] = e[i]  a[i-1]
    }
    
  • 不应该有特殊的运算符,也不应该有函数或子例程调用,除非这些函数或子例程调用是内联的,由编译器手动或自动内联,或者它们是 SIMD (向量化)函数。允许使用诸如 sin ()、 log ()、 fmax ()等内在数学函数,因为编译器运行时库包含这些函数的 SIMD (向量化)版本。更详细的列表请参阅评论部分。
  • 如果一个循环是循环巢穴的一部分,那么它通常应该是内循环。外部循环可以使用 OpenMP * 或者 autoparallelization (- parallel)并行化,但是它们很少能够自动向量化,除非编译器能够完全展开内部循环,或者能够交换内部和外部循环。(额外的高级循环转换,例如这些转换可能需要 -O3。这个选项适用于英特尔和非英特尔微处理器,但是它可能会导致英特尔微处理器列表处理器比非英特尔微处理器更优化)。SIMD 杂注或指令可用于要求编译器对外部循环进行向量化。阅读使用 # pragma SIMD 的向量化循环的需求,以获得关于使用 # pragma SIMD 向量化哪种循环的更多信息!4.0,# pragma omp SIMD and!$OMP SIMD.

建议:

  • 允许对数组进行减少和向量分配。
  • 尽量避免在同一个循环中混合可向量化的数据类型(数组下标的整数算法除外)。类型转换的向量化可能效率不高。
  • 尝试访问连续的内存位置。(循环遍历 Fortran 中的第一个数组索引,或者 c 中的最后一个数组索引)。虽然编译器通常能够使用间接或非单位步进存储器寻址对循环进行向量化,但从存储器收集数据或将数据分散到存储器的成本可能太高,不值得进行向量化。在 Fortran,“毗连”关键字可用于断言假定的形状数组或指针数组是毗连的。
  • “ ivdep”杂注或指令可以用来通知编译器,没有循环载入的依赖项会使向量化不安全。
  • “ vector always”杂注或指令可用于覆盖编译器的启发式方法,即评估循环的向量化是否可能产生性能优势。该杂注不重写编译器的依赖项分析。
  • 要查看循环是否已经向量化,以及为什么会这样,请查看优化报告的向量化组件。默认情况下,它被写入具有扩展名的文件。Optrpt.可以通过命令行交换器/Qopt-report: 2/Qopt-report-phase: vec (Windows *)或-Qopt-report = 2-Qopt-report-phase = vec (Linux * 或 macOS *)启用报告。可以通过将报告水平从2提高到5来获得更多的信息。
  • 要获得进一步的向量化建议和解释优化报告的帮助,请尝试在应用程序上运行 Intel Advisor。
  • 显式向量编程可以使循环的向量化更可预测,通过使用 SIMD 函数、 SIMD 语法和指令或者它们的 OpenMP 4.0对应物。