此文主要内容来自这篇文章,本文翻译只求能理解,不求逐句翻译。

正文:

我们将在本文中介绍如何在C++/C中使用SSE指令。我的目的不是用SSE写尽可能快的程序,而是试图讲明白它的使用方法。

什么是SSE?

SSE的全称是 Sreaming SIMD Extensions, 它是一组CPU指令,用于像信号处理、科学计算或者3D图形计算一样的应用。

SIMD 也是几个单词的首写字母组成的: Single Instruction, Multiple Data。 一个指令发出后,同一时刻被放到不同的数据上执行,

这个指令就是SIMD指令。

SSE在1999年首次出现在Pentium 3上。在过去的那段时光里,一些更加精致的功能被加入了这套指令集,

8个128-bit的寄存器被加入了CPU :xmm0到xmm7.

最初的时候,这些寄存器智能用来做单精度浮点数计算(float),

自从SSE2开始,这些寄存器可以被用来计算任何基本数据类型的数据了。

给定一个标准的32位机器,我们可以并行的存储和计算了:

-- 2 double

-- 2 long

-- 4 float

-- 4 int

-- 8 short

-- 16 char

注意:整数类型可以是有符号也可以是无符号的,不过有时候你可能要用不同的指令来处理他们。

比如,你想计算两个整数数组的和,你可以一次计算四个加法。

简单的例子

开始学习SSE并不是很简单的,幸好MSDN的文档写的很好(原作的链接打不开了,新连接是我加上去的)!

如果你看一下那个算术操作的列表,一会注意到总有相应的汇编指令与其对应。

另外,一些操作是符合操作,例如那些set操作

在C++中用SSE真真是一个low-level的操作:我们将直接通过类型

__m128(4个float)、__m128d(2个double)、__m128i(int、short、char)直接控制那些128-bit的寄存器。

不过,为了使用SSE我们不必去声明__m128类型的数组:比如,你想计算一个浮点型数组中每个元素的平方根,

有可以直接将你的数组强制类型转换成__m128*,然后使用SSE的命令操作这个数组。

不管怎样,我们还是要多做一点事情,才能用SSE。大多数SSE操作需要我们的数据是16-bytes对齐的,

这里我们将使用另一个GCC的 Variable attributes。 我们使用对齐属性:

  1. aligned (alignment)
  2. This attribute specifies a minimum alignment for the variable or structure field, measured in bytes.

下面是一个简单的代码,展示如何用SSE的_mm_sqrt_ps()函数一次性计算四个浮点数的平方根:

  1. float a[] __attribute__ ((aligned (16))) = { 41982.,  81.5091, 3.14, 42.666 };
  2. __m128* ptr = (__m128*)a;
  3. __m128 t = _mm_sqrt_ps(*ptr);

如果用GCC编译器,在编译选项中加入-S选项,产生的汇编代码中相应的汇编语句是SQRTPS,

而且这个指令使用的寄存器就是SSE的寄存器:

  1. sqrtps  %xmm0, %xmm0

不要忘了加上那个头文件:

  1. #include <emmintrin.h>

第一个评测

在前面的代码中,我们同时计算了4个float的平方根,但是我们没有记录结果。为了记录结果,我们使用_mm_store_ps

在下面的代码中,我们计算一个非常大的float数组的平方根。(作者使用的是他之前写的计时函数,这里我直接贴出来了)

来对程序的标准版本和SSE版计时。

  1. class Timer
  2. {
  3. public:
  4. Timer(const std::string& name)
  5. : name_ (name),
  6. start_ (std::clock())
  7. {
  8. }
  9. ~Timer()
  10. {
  11. double elapsed = (double(std::clock() - start_) / double(CLOCKS_PER_SEC));
  12. std::cout << name_ << ": " << int(elapsed * 1000) << "ms" << std::endl;
  13. }
  14. private:
  15. std::string name_;
  16. std::clock_t start_;
  17. };
  18. #define TIMER(name) Timer timer__(name);
  19. void normal(float* a, int N)
  20. {
  21. for (int i = 0; i < N; ++i)a[i] = sqrt(a[i]);
  22. }
  23. void sse(float* a, int N)
  24. {// We assume N % 4 == 0.
  25. int nb_iters = N / 4;
  26. __m128* ptr = (__m128*)a;
  27. for (int i = 0; i < nb_iters; ++i, ++ptr, a += 4)
  28. _mm_store_ps(a, _mm_sqrt_ps(*ptr));
  29. }
  30. int main(int argc, char** argv)
  31. {
  32. if (argc != 2)
  33. return 1;
  34. int N = atoi(argv[1]);
  35. float* a;
  36. posix_memalign((void**)&a, 16,  N * sizeof(float));
  37. for (int i = 0; i < N; ++i)a[i] = 3141592.65358;
  38. {
  39. TIMER("normal");
  40. normal(a, N);
  41. }
  42. for (int i = 0; i < N; ++i)a[i] = 3141592.65358;
  43. {
  44. TIMER("SSE");
  45. sse(a, N);
  46. }
  47. }

在上面的SSE的函数代码中,我们用了两个指针指向的是同一个地址,但是使用的类型不同,这当然不是必须的,只是用来避免强制类型转换。

有趣的是,我们必须对__m128每次递增1(128bits),对应的,我们也必须按四递增float指针(就是相当于一次算四个float)。

另一个有趣的函数式 posix_memalign,而不是用align attribute,这个函数是在堆上申请对齐内存,而gcc attribute是在栈上申请内存。

评测环境: llvm-g++ 4.2 (flags: -O3 -msse2)  在Intel Core2 Duo P7350(2GHz)上测试。

  1. $ ./sqrt 64000000
  2. normal: 392ms
  3. SSE: 145ms

真的相当快哈!

第二个评测

怎么将两个char数据加在一起呢:

  1. void sse(char* a, const char* b, int N)
  2. {
  3. int nb_iters = N / 16;
  4. __m128i* l = (__m128i*)a;
  5. __m128i* r = (__m128i*)b;
  6. for (int i = 0; i < nb_iters; ++i, ++l, ++r)
  7. _mm_store_si128(l, _mm_add_epi8(*l, *r));
  8. }

评测结果:

  1. $ ./add 64000000
  2. normal: 98ms
  3. SSE: 42ms

性能分析

你可能会问,为什么我们没有得到四倍的加速呢?我们可是一次计算4个float数据啊,怎么我们只有2倍的加速呢??

答案是,你的编译器很聪明,它已经做了很多优化了,特别是在加入O3选项后。

实际上,如果你看下normal产生的汇编代码,里面的sqrt和add函数都已经被你的编译器给用SSE指令优化了。

编译器检测到循环模式适合SSE,就把这个代码使用SSE指令实现了。

不管怎样,直接使用SSE函数还是可以获得一些性能的。

取决于你的编译器版本,对于这种简单的循环,你发现执行时间上没有差异也是可能的。

但是,这里必须要再提一次的是,我们是介绍怎么用SSE,不是只为了性能~

来源:http://blog.csdn.net/bendanban/article/details/42299863

http://blog.csdn.net/gengshenghong/article/details/7008704

SSE入门的更多相关文章

  1. 自然饱和度(Vibrance)算法的模拟实现及其SSE优化(附源码,可作为SSE图像入门,Vibrance算法也可用于简单的肤色调整)。

    Vibrance这个单词搜索翻译一般振动,抖动或者是响亮.活力,但是官方的词汇里还从来未出现过自然饱和度这个词,也不知道当时的Adobe中文翻译人员怎么会这样处理.但是我们看看PS对这个功能的解释: ...

  2. SSE图像算法优化系列八:自然饱和度(Vibrance)算法的模拟实现及其SSE优化(附源码,可作为SSE图像入门,Vibrance算法也可用于简单的肤色调整)。

    Vibrance这个单词搜索翻译一般振动,抖动或者是响亮.活力,但是官方的词汇里还从来未出现过自然饱和度这个词,也不知道当时的Adobe中文翻译人员怎么会这样处理.但是我们看看PS对这个功能的解释: ...

  3. SSE指令集学习:Compiler Intrinsic

    大多数的函数是在库中,Intrinsic Function却内嵌在编译器中(built in to the compiler). 1. Intrinsic Function Intrinsic Fun ...

  4. 【原创】新手入门一篇就够:从零开发移动端IM

    一.前言 IM发展至今,已是非常重要的互联网应用形态之一,尤其移动互联网时代,它正以无与论比的优势降低了沟通成本和沟通代价,对各种应用形态产生了深远影响. 做为IM开发者或即将成为IM开发者的技术人员 ...

  5. Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

    1. 前言 Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Serve ...

  6. SSE技术详解:一种全新的HTML5服务器推送事件技术

    前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...

  7. WebSocket学习笔记——无痛入门

    WebSocket学习笔记——无痛入门 标签: websocket 2014-04-09 22:05 4987人阅读 评论(1) 收藏 举报  分类: 物联网学习笔记(37)  版权声明:本文为博主原 ...

  8. 深度学习入门实战(二)-用TensorFlow训练线性回归

    欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者 :董超 上一篇文章我们介绍了 MxNet 的安装,但 MxNet 有个缺点,那就是文档不太全,用起来可能 ...

  9. ClickHouse 快速入门

    ClickHouse 是什么 ClickHouse 是一个开源的面向联机分析处理(OLAP, On-Line Analytical Processing) 的列式存储数据库管理系统. 在一个 &quo ...

随机推荐

  1. C#判断IP地址是否合法函数-使用正则表达式-2个 (转)

    public bool IsCorrenctIP(string ip){ string pattrn=@"(/d{1,2}|1/d/d|2[0-4]/d|25[0-5])/.(/d{1,2} ...

  2. ASP.NET是如何在IIS下工作的[转]

    ASP.NET与IIS是紧密联系的,由于IIS6.0与IIS7.0的工作方式的不同,导致ASP.NET的工作原理也发生了相应的变化. IIS6(IIS7的经典模式)与IIS7的集成模式的不同 IIS6 ...

  3. 如何使用 PagedList.Mvc 分页

    刚开始找PagedList分页不是例子太复杂,就是写的过于简略,由于对于MVC的分页不太了解,之前使用的都是Asp.Net 第三方控件 + 数据库存储过程分页.还是老外写的例子简捷,https://g ...

  4. UVa 10328 - Coin Toss (递推)

    题意:给你一个硬币,抛掷n次,问出现连续至少k个正面向上的情况有多少种. 原题中问出现连续至少k个H的情况,很难下手.我们可以试着将问题转化一下. 设dp[i][j]表示抛掷i个硬币出现连续至多j个H ...

  5. web api post传一个参数时 值永远是null

    这个问题纠结了我一个早上,不管用什么样的传参方法,走到控制器中,那个参数永远不变的等于null 在网上找了很多解决方案 上面这个是从网上截图的,第一:要将参数标记为[FromBody],变为简单参数 ...

  6. RabbitMQ、Redis

    进程QUEUE,可以是父进程与子进程间进行交互,也可以是同属于一个父进程的子进程间的交互:如果要实现进程A与进程B之间的通信,就需要借助一个中间进程了,我们习惯称为消息队列. QQ无法直接与WORD通 ...

  7. 第一课~Django~简介

    Django一个可以是Web开发工作者开发工作愉快并且高效的Web 框架 . 使用Django , 使你能够以最小的代建构建和维护高质量的Web应用 . 从好的方面来看 , Web开发激动人心并且富有 ...

  8. HTTP header头信息

    HTTP(HyperTextTransferProtocol)即超文本传输协议,目前网页传输的的通用协议.HTTP协议采用了请求/响应模型,浏览器或其他客户端发出请求,服务器给与响应.就整个网络资源传 ...

  9. IFrame 获取内容

    试试: iframe.contentwindow.document.documentElement.innerHTML   document.getElementById("MyIFrame ...

  10. Team Foundation API - 编程控制文件版本

    Team Foundation Server (TFS)工具的亮点之一是文件的版本控制.在TFS中实现文件版本控制的类型: Microsoft.TeamFoundation.Client.TfsTea ...