在Visual Studio中对C ++ OpenMP进行SIMD扩展

在无处不在的AI应用时代,对编译器的需求不断增长,以加速现有硬件的计算密集型机器学习代码。 此类代码通常执行数学计算,例如矩阵变换和操作,并且通常采用循环形式。 OpenMP的SIMD扩展通过明确利用现代处理器的向量单元,为用户提供了一种轻松的方式来加快循环速度。 我们很自豪地开始在Visual Studio 2019中提供C / C ++ OpenMP SIMD矢量化。


OpenMP C / C ++应用程序接口最初旨在通过在1990年代使代码在多个处理器上有效地并行执行来提高应用程序性能。 多年来,OpenMP标准已得到扩展,以支持其他概念,例如基于任务的并行化,SIMD矢量化和处理器卸载。 自2005年以来,Visual Studio已支持专注于多线程并行化的OpenMP 2.0标准。 随着世界进入AI时代,我们看到了通过扩展Visual Studio中对OpenMP标准的支持来提高代码质量的机会。 我们通过添加对OpenMP SIMD的支持来继续Visual Studio 2019的旅程。




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


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

如您所知,Visual Studio中的C ++已经提供了类似的非OpenMP循环编译指示,例如#pragma vector#pragma ivdep 。 但是,编译器可以使用OpenMP SIMD执行更多操作。 例如:


  1. 始终允许编译器忽略存在的任何矢量依赖关系。
  2. / fp:在循环内启用快速。
  3. 具有函数调用的循环是可矢量化的。
  4. 外循环是可矢量化的。
  5. 嵌套循环可合并为一个循环并进行矢量化。
  6. 使用#pragma omp for simd可实现混合加速以启用粗粒度多线程和细粒度矢量化。

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


  • simdlen( length ):指定矢量通道数
  • safelen( length ):指定向量依赖距离
  • 线性( list [ linear-step] ):从循环归纳变量到数组订阅的线性映射
  • aligned( 列表[ alignment] ):数据的对齐方式
  • 私有( 列表 ):指定数据私有化
  • lastprivate( list ):使用最后一次迭代的最终值指定数据私有化
  • 还原( reduce-identifier 列表 ):指定定制的还原操作
  • 收起( n ):合并循环嵌套

新的-openmp:实验性开关


可以使用新的CL开关-openmp:实验来编译带有OpenMP-SIMD注释的程序 此新开关启用了-openmp下不可用的其他OpenMP功能。 虽然此开关的名称是“实验性的”,但开关本身及其启用的功能已得到完全支持并可以投入生产。 该名称表明它没有启用OpenMP标准的任何完整子集或版本。 编译器的未来迭代可能会使用此开关来启用其他OpenMP功能,并且可能会添加新的与OpenMP相关的开关。 -openmp:实验性开关包含-openmp开关,这意味着它与所有OpenMP 2.0功能兼容。 请注意,SIMD指令及其子句无法使用-openmp开关进行编译。


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


cl -O2 -openmp:实验性mycode.cpp


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


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


对于矢量化的循环,除非提供矢量化日志记录开关,否则编译器将保持沉默:


cl -O2 -openmp:实验性-Qvec报告: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编译指令与新开关下的后端矢量化程序连接在一起。 我们致力于通过改进矢量化器和别名分析来对最内层的循环进行矢量化处理。 在撰写本文时,没有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); } 

因此,循环的以下变换矢量形式是合法的,因为编译器会保留每个原始循环迭代的顺序行为。 换句话说,在[-1]之后执行[i],在[i]之后执行b [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别名为[i]b [i],则将其移出循环是非法的。 如果它破坏了顺序依赖性,则在一次原始迭代中对语句重新排序也是非法的。 例如,以下转换循环是合法的。


 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)或开发者社区与我们联系


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


头像
于洪涛

Source: https://habr.com/ru/post/zh-CN446688/


All Articles