资源:https://download.csdn.net/download/Rong_Toa/18745608

目录

简介

背景

数据布局

在 Ticker Tape 中实施 SIMD 优化

点积示例

未来展望

更多文章/资源


简介


Ticker Tape 是一种技术演示,旨在鼓励开发人员在粒子系统中执行更为复杂的操作。参与该演示的开发人员会运用大量技术,来提高包括多线程和针对英特尔® SIMD 流指令扩展(SSE)的优化等在内的性能。请访问:/zh-cn/articles/tickertape-0 ,查看本文概述,下载本演示。本文将重点谈论通过在 Ticker Tape 演示中引入 SSE 指令所获得的性能提升。但在此之前,我们将首先介绍 SSE 编程,指导您规划您的数据,使之能为 SSE 带来最大优势。最后,我们将示范如何采用 SSE 计算点积.

背景


SSE 是一套专门为 SIMD(单指令多数据)架构设计的指令集。通过它,用户可以同时在多个数据片段上执行运算,实现数据并行(有时又称矢量处理)。例如,我们可以利用这套指令集使两个数组各自相乘:

float a[nElements], b[nElements], c[nElements];
for (unsigned int i = 0; i < nElements; i++) {c[i] = a[i] * b[i];
}

列表 1两个数组相乘的标量法其中每次迭代处理一个元素

一般而言,如列表 1 所示,存在一个让所有元素进行迭代的循环,在这个循环中每个元素会相互相乘,然后保存乘积。现在,我们除了可以在每次循环迭代时执行一个乘法运算外,还可以执行多个乘法运算。下面是可以执行多个乘法运算的函数 MultiplyFourElements()。

float a[nElements], b[nElements], c[nElements];void MultiplyFourElements(float *a, float *b, float *c) {c[0] = a[0] * b[0];c[1] = a[1] * b[1];c[2] = a[2] * b[2];c[3] = a[3] * b[3];
}for (unsigned int i = 0; i < nElements; i += 4) {MultiplyFourElements(&a[i], &b[i], &c[i]);
}

列表 2两个数组相乘的标量法其中每次迭代处理四个元素

如列表 2 所示,我们创建了一个可以同时处理四个元素的函数。利用该函数,我们执行循环迭代的次数减少了四倍。尽管如此,由于每次迭代所需执行的数学运算相同,我们在效率上并未获得较大提升。然而有了 SSE 指令,我们不再需要通过一个函数连续执行四次乘法运算,只需借助一条指令即可同时执行四个乘法运算。SSE 会使用处理器上的 128 位宽专用寄存器。这些寄存器可以保存任何 128 位数据,如两个双精度数字、四个单精度数字或 16 字节数字等。采用 SSE 进行编程的方式有两种:一种是直接编写汇编指令代码, 另一种是使用 intrinsics 函数编程。Ticker Tape 演示以及本文都将只侧重于使用 intrinsic 函数进行编程。使用 intrinsics 而非汇编指令编程是一种更加直接的方法,与标准 C/C++ 编程类似。此外,使用 intrinsics 编程还有助于编译器更好地优化代码,对此本文将稍后阐释。

// Assembly SSE Instruction
/// xmm0 and xmm1 are actual registers, not variables
mulps xmm0,xmm1// Intrinsic SSE Instruction
__m128 a, b, c;
c = _mm_mul_ps(a, b);

列表 3汇编指令与 Intrinsic SSE 指令
type __m128 是针对一个可映射至其中一个 SIMD 寄存器的 16 字节对齐变量的定义。该程序首先需要将已完成 _mm_load_ps() intrinsic(未在列表 3 中显示)运算的数据明确加载到 SIMD 寄存器中。_mm_mul_ps() 是实际执行运算的指令。一旦我们获得运算结果,我们可以利用 _mm_store_ps()intrinsic 将其作为输出数组保存下来。

#include <xmmintrin.h>float a[nElements], b[nElements], c[nElements];
__m128 A, B, C;//... load data in a, b
for (unsigned int i = 0; i < nElements; i += 4) {A = _mm_load_ps(&a[i]);B = _mm_load_ps(&b[i]);C = _mm_mul_ps(A, B);_mm_store_ps(&c[i], C);
}

列表 4两个数组相乘的 SIMD 方法
显然,运算差异已在图 1 中显示:

 1对比标量循环运算法与 SIMD 运算法

数据布局


许多应用的瓶颈并非在于算法的运算部分,而在于数据读写上。在上一个示例中,75% 的指令被用于加载和保存数据。一旦您想访问的数据保存在了不同的区域中,则需花费大量时间才能将该数据加载至高速缓存中并继而加载至 SIMD 寄存器中。例如,如果上一示例中的数组实际上是我们所拥有的数组类的成员变量,则将该数据加载至寄存器中会是一件十分困难且耗时的工作。各个元素将不得不被逐个加载至 SIMD 寄存器中。

class foo {float a;float b;... other data ...
};void bar() {foo fooArray[nElements];float c[nElements];__m128 A, B, C;// Non_SIMD Methodfor (unsigned int i = 0; i < nElements; i++) {c[i] = fooArray[i].a * fooArray[i].b;}// SIMD Methodfor (unsigned int i = 0; i < nElements; i += 4) {A = _mm_load_ps(&fooArray[i].a); // What will this do?B = _mm_load_ps(&fooArray[i].b); // Load incorrect dataC = _mm_mul_ps(A, B);_mm_store_ps(&c[i], C);}
}

列表 5支持 SIMD 不适用数据布局结构数组的类
在列表 5 中,数据加载方式出现了错误。该程序打算将连续内存加载至 SIMD 寄存器中,但这将会形成错误数据。要将数据加载至寄存器中不是不可能,但这样做涉及到重新安排数据的格式,而这需要一定的步骤才能完成。无论重新安排数据实际生成的处理成本如何,需要转移的数据很多,加载至内存的高速缓存行实际上也很多。然而,尽管如此,使用 SSE 指令仍然存在诸多优势,尤其是当相同的数据正在执行多项运算时。重新安排数据布局是指按照顺序安排类似数据片断,便于高效加载和保存这些数据。除此以外,安排数据布局的另一个优势是在 SIMD 寄存器尺寸增加时,改写代码会更轻松。这样一来,您便可以一次加载八个而非四个浮点数据。在上一示例的基础上采用更高效的数据布局进行改编后的程序如下:

class foo {float *a;float *b;... other data ...
};foo::foo(unsigned int nElements) {a = (float *)_mm_malloc(nElements * sizeof(float), 16);b = (float *)_mm_malloc(nElements * sizeof(float), 16);
}void bar() {foo fooVariable(nElements);float c[nElements];__m128 A, B, C;// Non_SIMD Methodfor (unsigned int i = 0; i < nElements; i++) {c[i] = fooVariable.a[i] * fooVariable.b[i];}// SIMD Methodfor (unsigned int i = 0; i < nElements; i += 4) {A = _mm_load_ps(&fooVariable.a[i]);B = _mm_load_ps(&fooVariable.b[i]);C = _mm_mul_ps(A, B);_mm_store_ps(&c[i], C);}
}

列表 6支持 SIMD 适用数据布局结构数组的类

 2SoA  AoS 内存布局

在 Ticker Tape 中实施 SIMD 优化


当我们通过优化 Ticker Tape 以发挥由两个重要部分组成的 SIMD 优势、重新安排数据使之更适用于 SIMD,以及重新编写部分代码以使用 SSE 指令时,我们对 Ticker Tape 演示进行了改造,最后发现我们的大部分时间花费在了 Newton() 函数上。这个函数主要计算各种力如何影响粒子。在原始架构中,每个粒子都会调用一次该函数,并会在每个角执行四次内部运算。在较高层面上进行的概述如下:

class RigidBody
{void Newton();// ... Bunch more functions ...D3DXVECTOR3 Position;D3DXVECTOR3 Rotation;// ... Lots of other data ...
};void RigidBody::Newton()
{for (unsigned int = 0; i < 4; i++){// ... some math goes on ...// Velocity_GroundD3DXVECTOR3 Vel_Ang_CM_global;D3DXVec3TransformCoord(&Vel_Ang_CM_global, &Velocity_Angular_CM, &this->LocalGlobal);D3DXVec3Cross(&tmp, &Radial_Vec, &(Vel_Ang_CM_global));Velocity_Ground = (this->Velocity_Linear_CM) + tmp;// ... more math ...}return;
}

列表 7原始 Ticker Tape 布局
每个粒子都代表着不同的对象并包含着各自的操作方法。此外,程序中还存在一个通过每个调用其成员方法的粒子进行迭代的循环。在向 SSE 进行迁移时首先应创建另外一个名为 NewtonArray() 的 Newton() 函数。这与原始布局并无二致,但却是在整个粒子数组而非一个粒子上创建的新函数。同时,还应创建包含整个数组的 RigidBody 类,以便程序从拥有 RigidBody 对象的数组迁移至一个拥有数组的 RigidBody。根据针对不同数据布局的测试,数据进一步分解成了各自的组成部分(如列表 8 所示)。

// Original
D3DXVECTOR3 rotation;// Half way
D3DXVECTOR3 *rotation;// Final
float *rotationX;
float *rotationY;
float *rotationZ;

列表 8改变数据布局

待数据重组后,NewtonArray() 已经被修改,可以使用 SSE 指令。实施这一修改的基本前提是存在嵌套循环结构。每个采用内部循环索引的变量一般都会被加载到 SIMD 寄存器中,但每个采用外部循环索引的变量却都会被重复加载到寄存器中。事实上,我们删除了内部循环(如列表 9 所示)。_mm_set1_ps() 指令与 _mm_load_ps() 指令类似,唯一的不同之处在于前者可加载一个浮点值并能将其复制到寄存器的四个分区中。

// Original code example
for (unsigned int i = 0; i < nParticles; i++) {for (unsigned int j = 0; j < 4; j++) {c = a[i] * b[j]; // Notice the different indexers}
}// SSE version, inner loop removed
for (unsigned int i = 0; i < nParticles; i++) {A = _mm_set1_ps(&a[i]); // copy the value a[i] into all 4 slotsB = _mm_load_ps(&b[i * 4]);C = _mm_mul_ps(A, B);_mm_store_ps(&c[i], C);
}

列表 9删除内部循环

即使只修改该函数的一小部分代码,实施上述优化的优势仍然十分明显。在完成所有优化操作后,我们计算了 NewtonArray() 函数的执行时间。通过使用支持 Visual Studio 2008 的微软编译器(MSVCC),函数执行速度提高了 1.8 倍。由于具备自动矢量化性能,采用英特尔® C++ 编译器(ICC)编写原始代码能够带来巨大优势。采用 ICC 编写手写 SSE 可使函数执行速度提高达 4.5 倍。


Compiler:

MSVCC

MSVCC + SSE

ICC

ICC + SSE

Time (ms):

16.3

8.9

9.9

3.6

Improvement:

1.0x

1.8x

1.6x

4.5x

 1函数 Newton 的执行时间以毫秒为单位

点积示例


本章节将演示如何使用 SSE 指令计算点积(实际上是四个点积),并简要介绍具体算法。所涉及的变量全部被命名为 xmm#,因为八个 SSE 寄存器的名称从 xmm0 到 xmm7 不等。尽管如此,您没有必要采用这种方式命名变量,也不用只保留八个变量。

// Dot Product
// Computes dot products on two arrays of vectors, 1 at a time
for (unsigned int i = 0; i < nElements; i++) {result[i] = v1[i].x * v2[i].x +v1[i].y * v2[i].y +v1[i].z * v2[i].z;
}// Dot Product
// Computes dot products on two arrays of vectors, 4 at a time
for (unsigned int i = 0; i < nElements; i += 4) {xmm0 = _mm_load_ps(X1 + i); // Load data into SIMD registersxmm1 = _mm_load_ps(Y1 + i);xmm2 = _mm_load_ps(Z1 + i);xmm3 = _mm_load_ps(X2 + i);xmm4 = _mm_load_ps(Y2 + i);xmm5 = _mm_load_ps(Z2 + i);xmm6 = _mm_mul_ps(xmm0, xmm3); // Multiply x's togetherxmm7 = _mm_mul_ps(xmm1, xmm4); // Multiply y's togetherxmm8 = _mm_mul_ps(xmm2, xmm5); // Multiply z's togetherxmm0 = _mm_add_ps(xmm6, xmm7); // Add all the values togetherxmm7 = _mm_add_ps(xmm0, xmm8);_mm_store_ps(result + i, xmm7); // Save the results
}

列表 10SSE 版点积示例

采用 _mm_load_ps() 指令运行上述算法首先要将所有数据加载到 SIMD 寄存器中。这样做会获得作为一项参数的数据加载地址。所有拥有 ps 后缀的指令都是单精度版本的指令。许多指令都拥有多个版本,如 _mm_load_pd() 指令还可以用于加载两个双精度数字。同样至关重要的是,数据必须为 16 字节对齐数据。如果不是 16 字节对齐数据,则必须采用一条不同的指令--_mm_loadu_ps() 来运行函数,但这样做不会带来相同的性能提升优势。
数据加载完毕后,应采用 _mm_mul_ps() 指令完成数字相乘运算。所相乘的元素来自两个寄存器的相匹配元素。

 3_mm_mul_ps() 图示

在两个数组的相对应元素互相相乘后,应将各个乘积相加。执行求和运算应采用 _mm_add_ps() 函数。它与求积函数的工作原理一样,唯一的不同是它解决数字相加问题。最后,应采用 _mm_store_ps() 将运算结果写入内存。需要再次提醒的是,写入地址必须可容纳 16 字节数据。请访问:/zh-cn/articles/tickertape-0,获取包含该函数的代码样本以及一个用于比较不同方法性能的简单测试框架。

未来展望


在 Ticker Tape 演示中显然存在 SSE 需要改进的方面。其中一个应该是它需要进一步支持对代码进行矢量化,而不应仅针对一个函数实施矢量化。此外,SSE 还应能与即将推出的英特尔® 高级矢量扩展指令集(英特尔® AVX)进行协作。另外一个值得探索的有趣领域是使用英特尔® C++ 编译器自动矢量化代码。目前存在一些可帮助编译器执行矢量化操作的代码模式和结构。经修改后的 Ticker Tape 代码可采用这些代码模式和结构,便于 SSE 仍然保持幕后运行状态。

更多文章/资源


  • Ticker Tape: A Scalable 3D Particle System with Wind and Air Resistance
  • Ticker Tape source code and binaries
  • SSE code samples and method comparisons
  • "Creating a Particle System with Streaming SIMD Extensions"
  • Intel Software Developer Manuals
  • AVX homepage

作者简介


Quentin Froemke 现任英特尔视觉计算软件部门的软件工程师,主要负责提供游戏领域的协助工作,以更有效地发挥多核技术以及其它英特尔技术的优势

Benefits of SIMD Programming | SIMD的优势相关推荐

  1. SIMD、SIMD、SIMT、MISD、MIMD详解与比较

    1.SISD SISD(Single Instruction Single Data stream)单指令流单数据流,计算机体系结构分类的一种. 按照计算机同时处于一个执行阶段的指令或数据的最大可能个 ...

  2. simd java_Java SIMD 单指令多数据流优化

    前段时间做了个在java中使用SIMD指令的性能优化验证,通过使用intel开发的新特性vector API,Java中也可以利用SIMD指令优化运算效率,Amazing! 模型训练的过程大概有以下几 ...

  3. SIMD 编程的优势与SIMD指令:SSE/AVX 与编程demo

    资源:https://download.csdn.net/download/Rong_Toa/18745608 <Benefits of SIMD Programming | SIMD的优势&g ...

  4. 2021年五月中旬推荐文章

    目录 <一文读懂 Linux 内存分配全过程> <Linux Page Cache> <Linux Kernel 2.4 Internals> <linux ...

  5. MMX, SSE(XMM,MXCSR,FXSAVE),SSE2,SSE3,AVX,AVX-512

    摘自<Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes1, 2A, 2B, 2C, 2 ...

  6. 超标量、超级流水线、超长指令字、向量机 SIMD

    1.超标量(Super Scalar)     将一条指令分成若干个周期处理以达到多条指令重叠处理,从而提高cpu部件利用 率的技术叫做标量流水技术.     超级标量是指cpu内一般能有多条流水线, ...

  7. 从编译到执行,C++如何开发SIMD友好的代码?

    一:名词解释 Flynn分类法 Flynn于1972年提出了计算平台的Flynn分类法,主要根据指令流和数据流来分类.按照Flynn分类法,计算平台共分为四种类型. 1.单指令流单数据流机器(SISD ...

  8. GCC中SIMD指令的应用方法

    X86架构上的多媒体应用开发,如果能够使用SIMD指令进行优化, 性能将大大提高.目前,IA-32的SIMD指令包括MMX,SSE,SSE2等几级. 在GCC的开发环境中,有几种使用SIMD指令的方式 ...

  9. MultiMedia eXtensions - MMX:第一套应用于英特尔 80x86 指令集的 SIMD 扩展

    https://softpixel.com/~cwright/programming/simd/mmx.php 目录 MMX - An Overview MMX - The Registers MMX ...

最新文章

  1. java事件驱动模型_Spring事件驱动模型详解
  2. pythontcp_TCP编程
  3. mybatisplus新增返回主键_第17期:索引设计(主键设计)
  4. 绒毛动物探测器:通过TensorFlow.js中的迁移学习识别浏览器中的自定义对象
  5. Android和.NET通用的AES算法
  6. 自动化办公-Python处理Excel生成试卷
  7. 五笔字根表识别码图_五笔字体识别码规则图 五笔输入法字根表
  8. linux磁盘扩容不影响原数据,linux 升级磁盘后扩容数据盘大小
  9. Bridging legacy APIs 桥接遗留API
  10. ipad 在线打代码 code-server
  11. 发起http请求raw格式
  12. java中的Cipher类
  13. 未完成:读书笔记01芝加哥大学论文写作指南_Kate L. Turabian
  14. 优盘复制进来为空_U盘问题 复制文件夹到U盘后,再打开就成空的了、
  15. 表单中的label标签
  16. 【转】DRY原则的误区
  17. 问题记录:jenkins构建时报错The goal you specified requires a project to execute but there is no POM in...
  18. 树莓派4B【RaspBerry Pi 4 Model B】系统安装及配置教程
  19. 5G NR LDPC编译码汇总
  20. Visual Studio 2017 Enterprise 发布 15.4 版本,离线安装包百度网盘下载。

热门文章

  1. 简述人工智能的发展历程图_简述华强北airpods的发展历程
  2. linux ftp 警告暗号话,ssh,FTP到远程服务器时,显示自定义的警告信息
  3. Centos查看与关闭防火墙
  4. leetcode题解538-把二叉搜索树转化为累加树
  5. GitBook的使用方式,快速创建网页文档
  6. 用mac的chrome浏览器调试Android手机的网页
  7. PostMan入门使用教程
  8. 转载:SQL server2005 里面没有management studio!下载SQL开发版本
  9. WinCE下音频频谱显示效果图
  10. UML学习笔记(一):UML简介