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);
}
如果内存引用 可能带有 *c 或 a[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);
}