|
|
|
|
移动端

2.4.7 数据级并行

《高性能并行珠玑:多核和众核编程方法》第2章从正确到正确&高效:Godunov格式的Hydro2D案例学习,本章将探讨一段科学模拟代码,这段代码是一个以气体动力学为基础的模拟程序。这份程序的输出结果正确,但(初始版本)性能欠佳。本节为大家介绍数据级并行。

作者:张云泉 等译来源:机械工业出版社|2017-11-14 17:56

技术沙龙 | 邀您于8月25日与国美/AWS/转转三位专家共同探讨小程序电商实战

2.4.7 数据级并行

数据级并行可以巨大地提升性能,这种收益与借助向量化减少的指令数量相关——简单地说,我们将向量化代码,尽可能让这份代码尽可能看上去像串行代码。

原始的代码依赖于编译器指令实现向量化,但是它糟糕的性能意味着程序中有大量的指令开销,编译器并不总是能够推测出程序员在代码中所表达的想法。

我们使用C++ SIMD类来实现流量计算和积分运算中的向量化,这是一种性能和开发工作量的合理妥协。使用这种方法可以清晰表达出如何向量化,而且不需要考虑内置函数的细节和汇编代码所带来的阻碍。

新代码使用宏SIMD_WIDTH作为底层硬件的SIMD宽度的占位符(对于处理器是2或者4,对于协处理器是8)。

SIMD 2路。参考代码使用的slab方法保证了所有的计算使用相同的数据布局(总是在slab的x方向)。由于该方法中x、y遍历的不同,本地更新和“旋转更新”求解的方法在这两个遍历上的行为是不同的。图2-21描述了这种方法中不同的SIMD方案。

为了实现上述想法,Intel编译器在<dvec.h>头文件中提供了C++ SIMD类。每个内核使用适当的SIMD类型(我们使用VREAL_T宏来简化)来进行并行化,同时对于例程的核本身来说这些在形式上几乎没有改变。

vstrip_prime()和vstrip_stable()函数与自身的串行代码几乎相同。而strip_

prime()和strip_stable()在进行x和y遍历时行为是相同的(除了步(stride)以及ρu和ρv变量的顺序),但x的更新要求沿着数据布局的方向进行遍历,这要求我们引入一个hstrip_stable()函数以稍微不同的方式处理循环缓冲区。这在图2-21c展示;另一种方案是像原始方案一样像y轴坐标一样重排x轴的数据,如图2-21b所示。

图2-22展示新的x遍历更新函数;其主要新特征是使用rotate_left_wm1()、rotate_left_wm2()函数,这个对函数分别用1、2改变第一个参数通道里的内容并使用最左边通道的第二个参数替换新“空”通道的内容。图2-23显示了tile_x_step()函数如何应用于SIMD。我们省略了新tile_y_step()的描述,可以预见的是,这将类似于新的tile_x_step(),除了边界的初始化以上使用vstrip_prime()和vstrip_stable()以外,它们将完全相同。

剥落和掩码。当考虑在任意网格维度上SIMD时,我们一定要看如何处理工作中的“剩余”部分(即,循环末尾的工作并没有填充完SIMD单元的数据)。不适当处理会导致硬件和软件在运行时出错。通常的方法是循环剥离——主循环随着SIMD的步伐而推进,一个单独、串行的循环处理剩下的东西。这种方法会带来一些额外的开销,特别是主循环循环长度减少、SIMD宽度增长的时候,但对于大网格,这样操作SIMD所带来的收益会变少。

另一种方法需要使用支持写掩码的指令集,比如Intel Xeon Phi协处理器。这里,一个位掩码决定了向量处理器的哪个通道被写进内存,哪些被跳过。使用这种结构,就有可能避免循环剥离。这种机制在处理SIMD处理器不同寄存器要表现不同行为的时候使用。

协处理器对每个通道掩码有最基本的支持。几乎每个向量指令都会接收一个掩码参数,同时,存储和操作这些掩码都会有专门的掩码寄存器。处理器为了达到这种效果必须混合指令,这意味着需要结合和交叉两个寄存器的内容。这就刺激了指令数量的增长。

控制分支。对于有效向量化来说,更加深远的挑战是控制分支——SIMD分支代码的处理。这个问题会在有数据依赖程序执行时产生,同时在SIMD中解通常会执行所有的分支,使用混合/掩码操作以保证结果的正确性。在运行较多代码时,SIMD的指令的收益会随之减少。

Godunov方法利用Riemann求解器抛出异常的方法避免控制分支;Newton-Raphson迭代不必一致收敛。一些输入可能要求更多的迭代,而这个会造成SIMD的控制分支问题。参见图2-24中向量化后的循环。

基于我们的观察,Riemann求解器中的Newton-Raphson迭代几乎没有出现控制分支的问题;参考代码对于求解器在迭代上有用户定义的限制,但是99%的Riemann计算收敛前只需要一次迭代。

对齐。数据对齐的代价会随着架构而不同,并且它们的影响严重依赖于SIMD操作的加载部分。在这份代码增加的部分中,对齐“块”分配非常简单;对于一些网格尺寸,填充行(可以使得网格的0列保持对齐)是非常有必要的。图2-25展示了优化实现中的初始化代码。

最后一步。通过减少中间储存“块”和算术优化的方法显示的算术强度的提升已经完全被我们的向量化策略实现。图2-26显示了每次增量优化后的性能,加上参考代码,每一部分都使得处理器加速了1.4~1.5倍,协处理器加速了2.2~4.4倍!在这一点,多亏了宽向量单元,使得协处理器得以改进,但是对于小尺寸问题,协处理器的表现并不优秀。协处理器有较高吞吐量时效率才会突出。


喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Linux服务器安全策略详解

Linux主要用于架设网络服务器。如今关于服务器和网站被黑客攻击的报告几乎每天都可以见到,而且随着网络应用的丰富多样,攻击的形式和方法...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊