SIMD 扩展

Visual C++ 目前支持 OpenMP 2.0 标准,不过 Visual Studio 2019 现在也提供 SIMD 功能。

注意

若要使用 SIMD,请使用 -openmp:experimental 开关进行编译,该开关使其他 OpenMP 功能在使用 -openmp 开关时不可用。

-openmp:experimental 开关包含 -openmp,这意味着它可使用所有 OpenMP 2.0 功能。

有关详细信息,请参阅 Visual Studio 中 C++ OpenMP 的 SIMD 扩展

Visual C++ 中的 OpenMP SIMD

OpenMP SIMD 在 OpenMP 4.0 标准中引入,旨在创建向量友好的循环。 通过在循环前使用 simd 指令,编译器可以忽略向量依赖项,使循环尽可能向量友好,并尊重用户同时执行多个循环迭代的意图。

    #pragma omp simd
    for (i = 0; i < count; i++)
    {
        a[i] = a[i-1] + 1;
        b[i] = *c + 1;
        bar(i);
    }

Visual C++ 提供类似的非 OpenMP 循环 pragma,例如 #pragma vector#pragma ivdep,但是通过 OpenMP SIMD,编译器可执行更多操作,例如:

  • 始终允许忽略当前的向量依赖项。
  • 在循环中启用 /fp:fast
  • 外部循环和带有函数调用的循环对向量友好。
  • 嵌套循环可合并为一个循环并对向量友好。
  • 使用 #pragma omp for simd 进行混合加速可实现粗粒度多线程和细粒度向量。

对于向量友好循环,除非使用向量支持日志开关,否则编译器将保持无提示:

    cl -O2 -openmp:experimental -Qvec-report:2 mycode.cpp
    mycode.cpp(84) : info C5002: Omp simd loop not vectorized due to reason '1200'
    mycode.cpp(90) : info C5002: Omp simd loop not vectorized due to reason '1200'
    mycode.cpp(96) : info C5001: Omp simd loop vectorized

对于非向量友好循环,编译器会向每个循环发出一条消息:

    cl -O2 -openmp:experimental mycode.cpp
    mycode.cpp(84) : info C5002: Omp simd loop not vectorized due to reason '1200'
    mycode.cpp(90) : info C5002: Omp simd loop not vectorized due to reason '1200'

子句

OpenMP SIMD 指令还可采用以下子句来增强向量支持:

指令 说明
simdlen(length) 指定向量通道数。
safelen(length) 指定向量依赖项距离。
linear(list[ : linear-step] 从循环归纳变量到数组订阅的线性映射。
aligned(list[ : alignment]) 数据的对齐方式。
private(list) 指定数据私有化。
lastprivate(list) 使用上次迭代的最终值指定数据私有化。
reduction(reduction-identifier:list) 指定自定义归约操作。
collapse(n) 合并循环嵌套。

注意

编译器会解析并忽略无效的 SIMD 子句,并发出警告。

例如,使用以下代码发出警告:

   #pragma omp simd simdlen(8)
   for (i = 0; i < count; i++)
   {
       a[i] = a[i-1] + 1;
       b[i] = *c + 1;
       bar(i);
   }
   warning C4849: OpenMP 'simdlen' clause ignored in 'simd' directive

示例

OpenMP SIMD 指令为用户提供一种方法来指示编译器使循环对向量友好。 通过使用 OpenMP SIMD 指令批注循环,用户打算同时执行多个循环迭代。

例如,以下循环使用 OpenMP SIMD 指令进行批注。 循环迭代之间没有完美的并行性,因为从 a[i] 到 a[i-1] 存在向后依赖关系,但是由于 SIMD 指令,编译器仍可将第一条语句的连续迭代打包到一个向量指令中,并且并行运行它们。

    #pragma omp simd
    for (i = 0; i < count; i++)
    {
        a[i] = a[i-1] + 1;
        b[i] = *c + 1;
        bar(i);
    }

因此,循环的以下转换向量形式是“合法的”,因为编译器保留每个原始循环迭代的顺序行为。 换句话说,a[i]a[-1] 之后执行,b[i]a[i] 之后执行,最后再调用 bar

    for (i = 0; i < count; i+=4)
    {
        a[i:i+3] = a[i-1:i+2] + 1;
        b[i:i+3] = *c + 1;
        bar(i);
        bar(i+1);
        bar(i+2);
        bar(i+3);
    }

如果内存引用 可能带有 *ca[i] 的别名,则将其移出循环是不合法的b[i]。 如果中断顺序依赖关系,那么在一个原始迭代中重新排序语句也是不合法的。 例如,以下转换循环不合法:

    c = b;
    t = *c;
    for (i = 0; i < count; i+=4)
    {
        a[i:i+3] = a[i-1:i+2] + 1;
        bar(i);            // illegal to reorder if bar[i] depends on b[i]
        b[i:i+3] = t + 1;  // illegal to move *c out of the loop
        bar(i+1);
        bar(i+2);
        bar(i+3);
    }

另请参阅

/openmp(启用 OpenMP 2.0 支持)