VisualStudio C++中的SIMD扩展

在无处不在的人工智能应用时代,对编译器提出了一个新的需求,即加速现有硬件的计算密集型机器学习代码。这类代码通常进行数学计算,如矩阵变换和操作,它通常是以循环的形式。OpenMP的SIMD扩展通过显式利用现代处理器的向量单元,为用户提供了一种毫不费力的加速循环的方法。我们自豪地开始在Visual Studio 2019中提供C/C++ OpenMP SIMD矢量化。

null

OpenMP MP/C/C++应用程序接口最初是为了使代码在20世纪90年代在多处理器上并行执行而提高应用程序性能。多年来,OpenMP标准已经扩展到支持其他概念,如基于任务的并行化、SIMD矢量化和处理器卸载。自2005年以来,visualstudio支持openmp2.0标准,该标准侧重于多线程并行化。随着世界进入人工智能时代,我们看到通过扩展VisualStudio中OpenMP标准的支持来提高代码质量的机会越来越多。我们通过添加对OpenMP SIMD的支持,在visualstudio 2019中继续我们的旅程。

OpenMP SIMD首先在OpenMP 4.0标准中引入,主要针对循环矢量化。根据我们的研究,它是迄今为止机器学习中应用最广泛的OpenMP特性。通过使用OpenMP SIMD指令对循环进行注释,编译器可以忽略向量依赖关系,并尽可能地对循环进行向量化。编译器尊重用户同时执行多个循环迭代的意图。

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

正如你所知道的,VisualStudio中的C++已经提供了类似的非OpenMP循环语法。 #杂注向量 #布拉格马伊夫德普 . 但是,编译器可以用OpenMP SIMD做更多的工作。例如:

  1. 编译器总是可以忽略存在的任何向量依赖关系。
  2. /fp:fast在循环中启用。
  3. 带有函数调用的循环是可矢量化的。
  4. 外部循环是可矢量化的。
  5. 嵌套循环可以合并成一个循环并进行向量化。
  6. 混合动力加速是可以实现的 #simd的pragma omp 支持粗粒度多线程和细粒度矢量化。

此外,OpenMP SIMD指令可以采用以下子句来进一步增强矢量化:

  • 辛德伦( 长度 ) : 指定矢量车道数
  • 安全( 长度 ) : 指定向量相关距离
  • 线性( 列表[ : [线性步长] ) : 回路诱导变量到阵列的线性映射
  • 对齐( 列表[ : 对齐] ): 数据的对齐
  • 私人( 列表 ) : 指定数据私有化
  • 最后一个私人( 列表 ) : 使用上一次迭代的最终值指定数据私有化
  • 还原( 还原标识符 : 列表 ) : 指定自定义缩减操作
  • 崩溃( n ) : 凝聚环巢

新建 -openmp:experimental 转换

可以使用新的CL开关编译OpenMP SIMD注释程序 -openmp:experimental. 此新交换机启用了在以下情况下不可用的其他OpenMP功能: -openmp接口 . 虽然此交换机的名称是“实验性的”,但交换机本身及其启用的功能完全受支持,并且可以投入生产。该名称反映了它没有启用任何完整的OpenMP标准子集或版本。编译器的未来迭代可能会使用此开关来启用其他OpenMP功能,并且可能会添加新的OpenMP相关开关。这个 -openmp:experimental 开关包含 -openmp接口 这意味着它与所有OpenMP 2.0功能兼容。请注意,SIMD指令及其子句不能用 -openmp接口 开关。

对于未矢量化的循环,编译器将为每个循环发出一条消息,如下所示。例如,

氯氧-openmp:experimental mycode.cpp

mycode.cpp(84):信息C5002:由于原因“1200”,Omp simd循环未矢量化

mycode.cpp(90):信息C5002:由于原因“1200”,Omp simd循环未矢量化

对于向量化的循环,除非提供了向量化日志开关,否则编译器将保持静默:

氯氧-openmp:experimental -Qvec-report:2 mycode.cpp

mycode.cpp(84):信息C5002:由于原因“1200”,Omp simd循环未矢量化

mycode.cpp(90):信息C5002:由于原因“1200”,Omp simd循环未矢量化

mycode.cpp(96):信息C5001:Omp simd循环矢量化

作为支持OpenMP SIMD的第一步,我们基本上已经将SIMD pragma与新交换机下的后端向量器连接起来。通过对矢量器和别名分析的改进,重点研究了最内层循环的矢量化。在撰写本文时,没有任何SIMD条款在Visual Studio 2019中有效。它们将被解析,但被编译器忽略,并发出警告以供用户注意。例如,编译器将发出

警告C4849:在“simd”指令中忽略OpenMP“simdlen”子句

对于以下代码:

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

有关OpenMP SIMD指令语义的详细信息

OpenMP SIMD指令为用户提供了一种指令编译器对循环进行矢量化的方法。编译器可以通过接受用户对正确性的承诺而忽略这种矢量化的明显合法性。当矢量化过程中出现意外行为时,这是用户的责任。通过使用OpenMP SIMD指令注释循环,用户希望同时执行多个循环迭代。这使编译器可以自由地生成机器代码,利用目标处理器上的SIMD或向量资源。虽然编译器不负责探索这种用户指定的并行性的正确性和收益,但它 必须 仍然确保单循环迭代的顺序行为。

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

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

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

#pragma omp simd
for (i = 1; 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[我] b[我] . 在一个原始迭代中重新排序语句也是非法的,如果它打破了顺序依赖关系。例如,下面的转换循环 合法的。

c = b;
t = *c;
#pragma omp simd
for (i = 1; 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 SIMD循环,您希望得到矢量化,但不是或生成的代码不是最佳的,请让我们知道。我们可以通过以下评论和电子邮件联系到您( visualcpp@microsoft.com ),twitter(@visualc),或通过 开发者社区 .

接下来,我们很高兴听到您对VisualStudio中缺少的OpenMP功能的需求。自从2.0标准以来,OpenMP已经有了一些主要的发展,OpenMP现在有了大量的特性来简化您构建高性能程序的工作。例如,从openmp3.0开始就可以使用基于任务的并发编程。OpenMP 4.0支持异构计算(CPU+加速器)。最新的OpenMP标准还提供了高级SIMD矢量化和DOACROSS循环并行化支持。请从OpenMP官方网站查看完整的标准修订版和功能集: https://www.openmp.org . 我们真诚地询问您对您希望看到的特定OpenMP功能的看法。我们还对您如何使用OpenMP加速代码感兴趣。您的反馈至关重要,它将有助于推动VisualStudio中OpenMP支持的方向。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享