我要投搞

标签云

收藏小站

爱尚经典语录、名言、句子、散文、日志、唯美图片

当前位置:四肖中特 > 反向掩码 >

利用256位英特尔®高级矢量扩展指令集(英特尔®AVX)实现

归档日期:07-27       文本归类:反向掩码      文章编辑:爱尚语录

  向开发人员介绍如何通过动态转置 3D 数据包来充分发挥 8-wide SIMD 处理的优势,进而提升几何计算的性能。

  本文描述了如何利用 256 位英特尔® 高级矢量扩展指令集(英特尔® AVX)实现 3D 矢量数组的标准化。此外,我们还介绍了在 AOS 和 SOA 之间进行动态转换,让数据用于 8-wide 单指令多数据(SIMD)处理的乱序方法。从 8x3 到 3x8 的转置可通过 5 个乱序操作来实现,而从 3x8 到 8x3 的转置则需要 6 个乱序操作。结果表明,使用更宽的 SIMD 执行标准化计算具备显著的优势,采用 128 位和256 位版本可分别将计算速度提高 2.3 和 2.9 倍。支持 SOA 处理的双向转置开销非常低(11 个额外指令),适合用于此类小型计算。

  许多交互式应用会在 CPU 上执行大量不同形式的几何处理。虽然数组结构(SOA)内存布局的处理速度最为高效,但在某些应用中,使用这种方式存储数据并不可行。常规 3D 矢量数组上经常运行有对性能要求极为严格的代码,是 3D 应用中极为普遍的一种数据结构。一个常见的例子是将一组 3D 标准矢量全部设置为单元长度。在本文的示例中,我们以该函数为起点,对代码进行了优化,借以深入挖掘 x86 处理器的先进功能,包括英特尔® 高级矢量扩展指令集(英特尔® AVX,第二代英特尔® 酷睿TM处理器家族全新微架构 Sandy Bridge 的一部分)中全新的 256 位代码。

  在实际应用中,这类例程可以使用带重载操作符的 C++ 3D 矢量类来实施,不过基本运算和数据布局完全相同。

  自英特尔® SIMD 流指令扩展 2(SSE2)于 2001 年面市以来,采用 4-wide 32 位精度浮点的单指令多数据(SIMD)处理得到了广泛应用。鉴于 128 位寄存器大小是处理 4D 数据的理想之选,大部分 3D 交互式应用日后均将采用同构矢量,或者使用额外的 32 位数据来填充 3D 矢量。虽然这种方法有助轻松实现 SIMD 处理,但出于其它性能瓶颈的影响,很难实现 4 倍的性能提升。在我们的标准化示例中,采用这种类型的 SIMD 只会占用一个 128 位寄存器的 3/4。虽然能够并行执行乘法运算,但如果要计算平方与平方根倒数的和,SIMD 不具备任何优势。最重要的是,这种编程模式无法扩展至更宽的 SIMD,例如支持 8 浮点数并行运算的 256 位英特尔高级矢量扩展指令集(AVX)。

  另一种部署 SIMD 处理的方法是一次处理循环的 8 个迭代,即同时对 8 个矢量执行标准化。这要求重新排列数据,将 8 个矢量的所有 X 分量、Y 分量和 Z 分量全部分别加载至三个独立的寄存器中。如果我们存储的数据恰好采用这种数组结构(SOA)形式,那么加载寄存器的过程将非常简单。然而,部分应用要求数据存储是打包的 3D 矢量序列,即结构数组(AOS)。因此,若想应用 8-wide SIMD 处理,我们需要动态地转置数据,进行计算,然后重新转置回来。本文介绍了如何在 x86 平台上通过乱序操作执行转置,并提供了性能测试结果,包括优化的串行和乱序转置实施,展示了采用英特尔 AVX 对 3D 矢量数组(AOS)进行标准化所带来的优势。

  要想将数据从一级高速缓存迁移至寄存器中,最为高效的方法是一次加载 128 位(或更多)数据。这种方法的跨度与我们的 3D 值数组包不同。不过,128 位对齐模式针对每 4 个矢量(或 12 个浮点)重复执行。也就是说,3 个对齐的 128 位加载操作会将接下来的 4 个 3D 矢量拖拉至 3 个 128 位寄存器中。

  经过三次加载操作后,矢量元素此时的顺序已没有太大用途。接下来,我们需要将来自 4 个 3D 矢量的数据转换为一种可用的形式,将 X、Y 和 Z 分量分组整合至独立的寄存器中。下图展示了采用 5 个英特尔 AVX 128 位乱序操作从 4x3 转置为 3x4 的过程。

  现在,数据处于 SOA 形式,计算步骤与串行实施相同,只是每次使用英特尔 AVX 指令针对 4 个矢量同时执行标准化。

  计算结果将为 SOA 形式,需重新转置为 AOS 形式。我们的转换运算并不是对称的。换句话说,从 SOA 到 AOS 的转置无法借助相同的代码实现。事实上,转置回来的过程需要一个额外的乱序操作。该流程如下图所示。

  到目前为止,我们已经讨论了 4 浮点或 128 位 SIMD 的使用方法。借助英特尔 AVX 中完整的 256 位寄存器和指令集,我们可以将这种方法扩展至 8x3。涉及乱序时,256 位英特尔 AVX 可被视为包括两条独立的 128 位通道。也就是说,256 位 AVX 提供了 2 条由 4 wide SIMD 单元组成的通道。与之前所述相同,我们将前 12 个浮点加载至 3 个 128 位寄存器中--这 3 个 128 位寄存器实际上是 3 个 256 位寄存器的下半部分。接下来,我们只需将后面的 12 个浮点(或 4 个 3D 矢量)加载至 3 个 256 位寄存器的上半部分即可。乱序计算步骤与 128 位版本相同,不同之处在于所采用的是各指令/内联函数(intrinsics)的 256 位版本。

  使用 256 位指令通过 6 个乱序操作将数据重新转置为 AOS 形式以及存储数据的过程都是 128 位版本的自然扩展。借助在 SOA 和 AOS 形式之间自由转换的能力,我们目前可以利用该技术实现 3D 矢量数组的标准化。

  执行测试的过程中,我们使用了来自英特尔® 处理器架构系统的单个内核,该系统采用了支持英特尔 AVX 的 Sandy Bridge 微架构。为了深入分析指令开销,我们在一级高速缓存中存储了 1024 个 3D 矢量。每次循环之前和之后,我们都会调用 RDTSC 指令,用以测量处理整个数据集所需的周期总数。在下方的表格中,我们将周期总数除以数组大小,得到了吞吐率数值,即每个经处理的矢量所需要的 CPU 周期数。除了各种标准化实施之外,我们还对删除了部分组件的循环进行了测量,计算出了每一步的开销。

  表中的数值是每个 3D 矢量消耗的 CPU 周期数。请注意,在每个循环迭代中,不只一个矢量被转置。在所有两种情况下,每个循环迭代需消耗 12 个周期,这在合理范围内,因为每个循环共包括 11 个乱序操作。有意义的测量结果是,在从结构数组包到数组结构再回到结构数组包的双向转换中,每个矢量的开销为三个或更少的周期。

  在接下来的测试中,我们希望测量一下不需要转置时的开销,为此我们创建了另一个数据结构。在该数据结构中,数据已经事先按照 SOA 形式排列,即所有 X、Y 和 Z 分量全部分别位于三个独立的数组中。下表列出了对已是 SOA 形式的 3D 矢量进行标准化的开销。

  结果显示了针对预先格式化的 SOA 数据执行 128 位和 256 位 SIMD 标准化实施的开销。鉴于混编数据的开销切实存在,对 AOS 数据进行标准化时是否能够实现如此快的速度值得怀疑。此处的结果只用作参考目的,大概体现了我们的循环只执行数学计算时的绝对性能极限。

  请注意,标准化实施采用的数字精度级别较低。平方根倒数近似值汇编例程的计算结果并没有使用 Newton-Raphson 提高精确度。对于部分应用而言,例如生成用于光照计算的网格法线,较低的精确度便已足够。这些测试的目的是评估动态转置技巧可为极小型计算带来哪些潜在优势。

  上表列出了多种不同实施的测试结果。为了获得一个公平的对比基准,我们使用精选汇编指令编写了一个优化的串行实施,精确度较低,平均每个循环需要 8 个周期或者每个矢量标准化执行需要 8 个周期。此外,出于自身的兴趣,我们还对比了另外一种采用掩码和位逻辑运算实施转置的 128 位 AOS-SOA-AOS 编程模式。在当前硬件上,这种编程模式的速度比串行实施要慢。

  采用英特尔 AVX 的 256 位 SIMD 时,对常规 3D 矢量数组进行标准化的吞吐率为 2.7 个周期。换言之,使用乱序操作执行 SIMD 实施比最佳串行实施的效果还要出色。请注意,开销值并不正好是前两个表中计算量和乱序操作的总和。英特尔的 x86 CPU 具备独立的乱序、乘法、加法和迁移执行端口。整体而言,基于乱序操作的转置能够显著提高 SOA 实施的速度。

  借助官方原始(vanilla)C/C++ 实施,开发人员可通过一系列循序渐进的步骤来提升代码性能,包括设置编译器标志以使用可用架构/指令,在实施过程中采用更快的指令,以及充分发挥 SIMD 处理的优势。从简单的 Release 模式编译到目前的 256 位英特尔® AVX 版本,性能提升了超过一个数量级。

  显而易见,采用已经是 SOA 布局的数据结构无疑是最高效的方式,但这在大多数应用中都不可行。动态地将 3D 数据在 SOA 和 AOS 形式之间进行转换能够有效地提高英特尔® CPU 处理器利用率和 3D 应用运行速度。在该示例中,每个矢量的计算量都非常小。如果需要在最内部循环中执行的工作量增多,通过这种方式所实现的增速幅度可能会更大。

  Stan Melax 于上世纪 90 年代初获得加拿大阿尔伯塔大学计算机科学专业的学士和硕士学位。在他的职业生涯中,大部分时间都贡献给了游戏行业,曾在 BioWare、EA 和 Ageia 等多家公司任职。Stan 现在是英特尔公司的显卡软件工程师,致力于帮助开发人员更加快速地编写代码,开发更为出色的应用。

  C 源代码采用可将 AOS 转换为 SOA的 128 位英特尔® 高级矢量指令集(英特尔® AVX)内联函数:

  C 源代码采用可将 SOA 转换为 AOS 的 128 位英特尔® 高级矢量指令集(英特尔® AVX)内联函数:

  包含这 4 个矢量的数据的寄存器 x、y、z 经过乱序操作,存储至以指针 p 开始的数组包中。

  C 源代码采用可将 AOS 转换为 SOA的  256 位英特尔® 高级矢量指令集(英特尔® AVX)内联函数:

  从地址 p 加载 8 个 3D 矢量,输出存储在 __m256 寄存器 x、y 和 z 中。虽然这看上去很复杂,实际上只是 128 位版本的自然扩展。

  C 源代码采用可将 SOA 转换为 AOS 的  256 位英特尔® 高级矢量指令集(英特尔® AVX)内联函数:

  包含 8 个 3D 矢量的数据的寄存器 x、y、z 经过乱序操作,存储至以指针 p 开始的数组包中。

本文链接:http://pebeducation.com/fanxiangyanma/457.html