阅读更多

0顶
0踩

编程语言

原创新闻 使用SIMD技术提高C++程序性能

2017-02-10 09:59 by 副主编 jihong10102006 评论(0) 有5155人浏览
c++
引用
作者:张银奎,《软件调试》和《格蠹汇编》作者,从事软件开发和研究十余年,对IA-32架构、操作系统内核、虚拟技术,尤其对软件调试有较深入的研究。微博ID:@dbgger
本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅《程序员》

今天和C++同行谈谈如何提高自己的身价。编程语言只是工具,要把工具用到最合适的地方,扬其长且避其短,打造出有价值的软件产品,那么使用这工具的人自然身价就高了。

C++语言的优势
C++语言有哪些优势呢?当然有很多,不然早消亡了。但泛泛地说“有很多”没意义。到底有哪些呢?不同人可能有不同的回答。同一个人在不同时间也可能回答不同。这里我们不妨听听C++之父Bjarne Stroustrup先生如何说。在前不久举办的“2016 C++及系统软件技术大会”上,Bjarne先生做了题为《What C++ is and what it will become》的主题演讲。他很深刻地阐释了到底什么是C++,解说了C++语言的根本特征和优势。有一页讲稿的题目叫“C++ in two lines”(用两行话来描述C++),可谓画龙点睛之笔。这两行是什么呢?第一行是“Direct map to hardware”,第二行是“Zero overhead abstraction”。粗浅翻译一下,就是“直接映射到硬件”和“0负担抽象”。

“直接映射到硬件”是说C++语言和硬件之间直接对应,C++的语句可以直接对应到CPU指令,C++的数据类型也可以直接对应到CPU支持的数据类型。也就是说,C++语言的代码和数据可以很直接地翻译到CPU支持的指令和数据,不需要做这样那样的转换。概言之,就是效率很高。

单纯从效率来讲,最直接的语言当然要属汇编,但汇编语言的问题是缺乏高级抽象,代码难以组织。所以Bjarne先生的第二行是“0负担抽象”,我想这一行又包含两层意思,第一层意思是C++是有抽象的,第二层意思是支持抽象的负担为0。在今天的软件大生产时代,即使是比较小型的软件项目,使用抽象也是必须的。今天流行的编程语言几乎无不支持抽象,不论Java还是C#、F#。虽然都支持抽象,但有些语言为了抽象付出的代价很大,资源消耗多,运行速度慢,也就是额外开销高。而C++是0负担抽象,0额外开销。Bjarne先生一定也知道Java的流行,一定也听见过关于C++和Java孰是孰非的争论。说C++是0额外开销略微有些夸张,但相对其他额外开销高的语言来说,相当于是0了。不这样说的话,说抽象负担远远低于其他语言,岂不啰嗦?一代宗师此言一出,言简意赅,料也无人敢出来说半个不是。

图1 C++之父Bjarne先生在介绍C++的根本优势


这两行话简单有力,一个是效率高,一个是负担小,加起来一起托出C++的核心优势:锐利无比,性能一流。

怀着虔诚的心,我一边聆听Bjarne先生论述上面两行话,一边用手机拍下了这个历史瞬间。地点是上海浦东外高桥的喜来登酒店,时间是2016年10月28日上午10点12分。

大师的话值得反复体味,每个C++程序员都应该牢记。在做设计时,我们应该用这两句话来检查自己的设计,是否大道至简,能否发挥硬件的性能。编写和检查代码时,我们更应该想起这两句话,把杂乱的逻辑理顺,把冗余的代码删掉,反复优化每行代码,把硬件的性能发挥到极致。不然的话,怎么对得起C++语言,怎么对得起Bjarne先生?

图2 C++之父Bjarne先生希望加入到C++17中的语言特征

有人说,硬件太强大了,写代码时不需要那么在意性能问题。这话真是放屁,每一位有责任心的C++程序员都应该站出来痛斥这样的言论。

在Bjarne先生介绍C++的未来发展时,他谈到了最希望加入到C++17中的语言特征。在这个包含十项内容的列表中,第8项是SIMD向量和并行化算法。刚好与我为准备的讲题不谋而合。

SIMD是Single Instruction,Multiple Data的缩写——意为单指令多数据。我是在Intel工作期间听说这个技术的,好多年前了,什么场合已经记不清,但仍记得当时眼前一亮,仿佛看到一束霞光,心中赞叹这种做法太美妙了。SIMD思想的最大特色和魅力是简单自然——人法地,地法天,天法道,道法自然。为什么如此说呢?因为SIMD思想的应用实例在我们的生活中随处可见,其出现年代或许可以追溯到原始社会。

我喜欢吃面包,因此以面包作坊为例。假设某作坊要做1000个面包,每个面包要经过成型、入炉、烘烤、取出等步骤。如果每次只操作一个面包,那么大多数基本动作都要重复1000次。但如果使用一个图3所示的简单模具,上面有四个“包厢(面包之厢,姑且称此名)”,那么便可以一次操作四个面包。这样一来,很多操作便都是成批的了,一次成型四个,向烤箱里放时,一次四个,拿出来时,又是一次四个,于是很多操作的重复次数便从1000次下降到了250,生产效率大大提高。

图3 SIMD思想源于生活


走进生活,其实有很多类似例子,我小时候看过用黄泥做土坯的劳动场面,一个称为坯模子的木框,上面有多个格子,每个格子对应一块土坯。如果到工厂里看一下,那么就更多了。很多工序都在使用SIMD思想,成批地生产。劳动激发智慧,软件同行们应该好好向现实社会学习。

SIMD在计算机领域应用也有很多年了,目前比较普遍地认为著名的ILLIAC IV大型机是SIMD思想的在现代计算机中的最早实现。ILLIAC IV由美国Illinois大学设计,宝来公司(Burroughs)建造,项目从1965年开始,经历六年时间耗资四千万美元完成,安装在美国国家航空航天局(NASA),服役多年,直到1981年才停机退役。

源于ILLIAC IV使用手册的图4既描述了ILLIAC IV的核心部件,又阐释了它的工作原理。图中的PE是Processor Element的缩写,即处理器单元,其功能和命名都与我们今天所说的Execution Unit(EU)(GPU内的基本执行单元)非常类似。PEM是Processing Element Memory,即处理单元记忆体,用来存放要计算的数据和计算结果。

图4 ILLIAC IV工作原理


ILLIAC IV有64个PE(处理器单元),可以同时做64个计算。图中演示的便是把数组B和数组C相加,结果放到数组A,一次便完成64个元素。

在ILLIAC IV手册里,还可以看到一些有趣的插图,比如图5这幅卡通画非常形象地描绘了ILLIAC IV可以成批处理数据的能力,工作人员用大叉子把文件一大摞一大摞地投给ILLIAC IV的大嘴。ILLIAC IV的众多计算单元排成一行在等待“喂食”。

IA CPU上SIMD实现
刚才介绍了SIMD的思想以及最早的实现,接下来先总览Intel架构(简称IA)上的SIMD实现,然后再给出实例。

很多人不知道SIMD是什么,但知道MMX。MMX是MultiMedia eXtensions的缩写,意思是多媒体扩展。1997年推出的基于P5架构的奔腾处理器是包含MMX技术的第一款IA CPU。从硬件角度来看,MMX技术包含8个64位的寄存器,称为MM0-MM7,可以对8个单字节整数,或者4个字,或者2个双字做各种组合操作。MMX仅支持整数类型,不支持浮点数。

1999年随同奔腾II处理器推出的SSE(Streaming SIMD Extensions)技术弥补了MMX不支持浮点数的不足,并对MMX做了很多改进,包括把寄存器的宽度扩展为128位,并且不再复用x87寄存器,并引入了70条新的指令。

2008年公布的AVX(Advanced Vector Extensions)进一步把寄存器的宽度扩展为256位,并且革新了指令格式,支持三目运算符。2011年发布的Sandy Bridge处理器包含了AVX技术。

图5 描述ILLIAC IV强大并行处理能力的卡通画


图6 IA CPU上的SIMD实现


“天河二号”是由国防科学技术大学研制的超级计算机系统,峰值计算速度可以达到每秒5.49亿亿次、持续计算速度每秒3.39亿亿次。在2014年11月17日公布的全球超级计算机500强榜单中,“天河二号”名列冠军。天河二号是个庞大的系统,由数以万计的计算节点组成,据说每个节点包含两颗Xeon处理器和三块Xeon Phi协处理计算卡。Xeon Phi具有强大的并行计算能力,其核心技术便是名为AVX-512的SIMD技术。AVX-512是对AVX的再扩展,寄存器的宽度扩展为512位,数量也加大一倍,从16个提高到32个。

至此我们已经介绍了SIMD的思想、历史以及在X86架构CPU的实现,接下来将通过一个具体的图像处理实例来介绍如何把SIMD技术应用到C++项目里,探讨难易不同的多种方案、以及如何衡量所取得的性能提升。

在C++程序中使用SIMD技术
图7列出了使用SIMD技术的多种方法,我们先按从上至下的顺序简要介绍每一种,然后重点介绍汇编语言方法。

图7 使用SIMD技术的多种方法


第一种方法是使用著名的IPP库,IPP的全称是Intel Integrated Performance Primitives, 是英特尔公司开发的一套跨平台软件函数库,提供了非常广泛的功能,包括各种常用的图形图像、音视频处理函数。因为其中的很多函数都已经使用SIMD技术做了优化,所以使用这个库是使用SIMD技术的一个快捷途径。通过链接https://software.intel.com/en-us/intel-ipp/ 可以访问IPP的官方介绍,了解更多信息。

第二种方法是使用编译器的自动向量化(Auto-vectorization)支持。比如图8是在Visual Studio(C++)中通过项目属性对话框启用自动向量化的截图。

图8 在Visual Studio中启用自动向量化支持


经笔者分析,这样启用后编译好的程序中确实使用了一些SIMD指令,比如图9右侧蓝色加亮那一行使用的便是SSE2中的cvtsi2sd指令,它可以将源操作数中的有符号双字整数转换成目标操作数中的双精度浮点值。

图9 观察编译器自动向量化产生的SIMD指令


如果使用GCC编译器,那么可以使用类似这样的命令行来编译:
代码1
如果希望看到编译器所采取的向量化动作,那么可以增加-ftree-vectorizer-verbose=1,于是可以类似图10的输出信息。

图10 使用GCC的自动向量化支持


使用GDB的反汇编功能,可以很容易地观察到GCC产生的SIMD指令,如图11所示。

图11 GCC的自动向量化功能产生的汇编指令


第三种方法是使用编译器指示符(compiler directive),比如,如果使用英特尔的C/C++编译器(ICC)编译如下代码,那么ICC便会对#pragma simd指示符下面的for循环做向量化,并给出类似下面这样的输出信息:remark: SIMD LOOP WAS VECTORIZED.

代码2
第四种方法是使用Cilk技术。Cilk一词源于发音相近的Silk一词,蕴含的意思是要把并行编程做的像丝绸一样美丽。Cilk技术最早由MIT开发,第一版本于1994年发布。后来开发者创建了一个名叫 Cilk Arts的公司,推出改进的私有版本。2009年,英特尔收购了Cilk Arts,将Cilk技术整合进英特尔编译器中。2012年后,Cilk再次成为开源项目,GCC中便有支持(需要4.8或者更高版本)。感兴趣的朋友可以从https://www.cilkplus.org网站了解更多信息和下载有关工具及示例代码。

第五种方法是使用编译器的内建函数(intrinsic),举例来说,下面这个循环来自我们要详细讨论的图像二值化程序的C++代码。

代码3
如果使用Visual C++编译器的SIMD intrinsic进行改写,那么新的代码如清单1所示。

清单1 通过intrinsic使用SIMD技术

代码4
第六种方法是直接使用汇编语言编写汇编函数,然后再从C++代码中调用汇编函数,稍后会详细介绍。

比较图7中的六种方法,灵活度和可控性由上至下越来越高,但是使用的难度基本也是越来越大。


编写和调试供SIMD汇编函数
有两种方法可以在C++项目中使用汇编代码,一种是通过__asm{}这样的指示符号把汇编代码嵌入在C++函数中,另一种是把汇编代码放在单独的以.asm结尾的文件中。前一种方法因为不支持64位,所以基本过时了。

在使用后一种方法时,首先要在项目的Solution Explorer树形控件上右击希望加入汇编文件的项目,然后选择Build Dependencies → Build Customizations调出图6所示的对话框,然后选中masm行。

详细讨论如何编写汇编代码超出了本文的范围,这里只能管中窥豹,介绍与上面讨论的for循环(清单1上方)对应的一段汇编指令(引自《现代x86汇编语言程序设计》一书),如清单2所示。

清单2 对灰度图像进行二值化处理的SSE2汇编程序片段

代码5
对于长久没有写过悉汇编代码的同行,理解清单2中的代码可能有些困难,特别是其中的SIMD指令。下面将以笔者惯用的调试方法来帮助大家理解——在调试器里看SIMD。

在清单2的第一行指令处设置断点(与在C++代码中设置断点方法相同),触发程序调用这个汇编函数,断点命中后,单步走过这条指令。

代码6
简单说,这条指令就是把edx指向的ITD结构体的Threshold字段赋给EAX寄存器。

打开汇编窗口,编译后的指令为:

代码7
这意味着,Threshold字段在结构体中的偏移是0xC。Ctrl + Alt + A调出Visual Studio的命令窗口,观察内存和寄存器的值,可以印证:

代码8
看来,这条指令的作用就是把二值化的阈值加载到EAX寄存器。

接下来一条指令比较简单,movd xmm1,eax,就是把常规寄存器EAX中存放的阈值传递给SSE的SSE寄存器XMM1。Visual Studio的寄存器窗口默认不显示SIMD的寄存器,但是可以通过快捷菜单很容易解决这个问题,点击右键,调出图12所示的快捷菜单,选中SSE即可。

图12 配置显示SSE寄存器


选中SSE后,单步执行,再观察寄存器窗口,可以看到XMM1的值如下:

代码9
接下来的两条指令是要把已经在XMM1最低字节中的阈值(0x98)散列(shuffle)到其他字节中。

代码10
两条指令中的p代表packed,即组合数,是SIMD中的常见术语,pshufb是Packed Shuffle Bytes的缩写,它根据第二个操作数指定的控制掩码对第一个操作数执行散列操作,产生一个组合数。描述起来比较拗口,单步执行这两条指令后看一下效果大家就明白了:

代码11
有趣吧。接下来的这条指令(movdqa xmm2,xmmword ptr [PixelScale])是把PixelScale常量数组中的缩放值赋给XMM2。

代码12
执行后,XMM2的值为:

代码13
接下来的指令是做组合减法,最有SIMD特色的操作。

代码14
单步前两个寄存器的值为:

代码15
单步后为:

代码16
也就是一次完成16个整数减法。

做好准备工作后,接下来就开始处理ESI指向的图像数据了,movdqa xmm0,[esi]每次可以把16个字节加载到XMM0,psubb xmm0,xmm2减去缩放值(128),然后使用下面这条pcmpgtb指令进行比较。

代码17
pcmpgtb的全称是Compare packed signed byte integers for greater than,它会根据比较结果来把目标字节写为全0或者全1(大于)。例如,单步前的XMM0和XMM1如果是:

代码18
那么单步后便是:

代码19
而后,movdqa [edi],xmm0指令把结果写到EDI指向的目标缓冲区中。然后把ESI和EDI都增加16,进行下一次循环。

图13是比较编写的测试程序界面,列表控件中包含了以不同方式对同一幅图像执行二值化操作测量到的时间。

图13 比较不同计算方式的测试程序


从图13可以看到,与普通的C++代码(图中以ALU表示)相比,使用SSE方法的速度提升是非常明显的,从原来的1000多毫秒,加快到了10/20多毫秒,这就是SIMD的魅力。篇幅关系,要就此打住了,感兴趣的朋友可以下载示例程序的完整源代码(http://advdbg.org/的资源板块)亲自体验一下。

订阅2017年程序员(含iOS、Android及印刷版)请访问 http://dingyue.programmer.com.cn
  • 大小: 181.8 KB
  • 大小: 208.9 KB
  • 大小: 425.2 KB
  • 大小: 151.6 KB
  • 大小: 243.6 KB
  • 大小: 205 KB
  • 大小: 63.5 KB
  • 大小: 121.5 KB
  • 大小: 130.4 KB
  • 大小: 34.2 KB
  • 大小: 141.6 KB
  • 大小: 63.4 KB
  • 大小: 109.6 KB
  • 大小: 77.4 KB
  • 大小: 252.5 KB
  • 大小: 95.5 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 怎么防止网站图片被盗链?

    要防止网站图片被盗链,那么就有必要了解?链的手段,基本上可以分为两种: 第一种:盗链者直接使用图片链接引用网站上的图片。防止这种盗链情况的发生有两种可行的方法:通过Apache服务器进行控制和使用SESSION变量在程序中进行控制。 第二种:直接从网站上下载图片并且复制使用。 防止图像盗链的方法主要有两种解决方案,一种使用Apache的mod_rewrite.so扩展,另一种使用PHP的...

  • H5 img标签图片无法显示 -- 解决方案

    如果你也曾复制过一些类似腾讯、百度等各大网站的文章,你会发现黏贴下来发表之后经常出现图片无法显示,这是怎么出现的呢?例如:<body> <img src="http://upload.techweb.com.cn/2017/0106/1483690985396.jpg" /> </body>解决方案使用Referer Meta标签控制referer,在H5 的 header加入meta<me

  • apache安全设置——某个目录只允许访问图片,其他类型文件不允许访问

    ServerAdmin webmaster@dummy-host2.localhost     DocumentRoot "F:/PHP/dztest"     ServerName www.dztest.com     ErrorLog "logs/dztest-error.log"     CustomLog "logs/dztest-access.log" common    

  • !!!!!如何配置Apache 防止图片盗链!!!!!

    https://blog.csdn.net/zhaokuner/article/details/7741610

  • 修改.htaccess文件禁止图片盗链、禁止资源盗链

    <br />图片等资源被盗链后,会极大的浪费我们的流量,即使虚拟主机不限制流量,也会浪费我们的带宽。今天,在网上偶尔看到了一篇防止盗链的文章,实践了一下,果然有效果,现将我的实践过程分享一下,仅供参考。<br />1.Apache中的.htaccess文件<br />       .htaccess文件,也叫“分布式配置文件”,是Apache中相当重要的配置文件,格式为纯文本,提供了针对目录改变配置的方法,通过对一个特定的文档目录中放置一个该文件,以作用于该目录及其子目录。<br />       通过

  • PHP解决盗链图片无法显示的问题

    PHP解决盗链图片无法显示的问题 header('Content-type: image/jpeg'); $context=array('http' => array ('header'=> 'Referer: http://www.baidu.com/',),); $xcontext = stream_context_create($context); echo $s

  • HTTP协议之防盗链与反防盗链

    仅供HTTP协议的初学者了解。 使用HTTP协议。利用referer做防盗链(不需要用php编写,而是在服务器层面控制就OK了) 我们在网页里访问站外的图片的时候,在图片本站是可以看得,在外头就不能看了 因为header信息中的referer元素。 还有是做统计的时候, 我们能够统计出来用户是从哪个地方,什么时间访问网站的。比如腾讯分析网站 统计的时候是靠什么知道用户从什么地方去的网站

  • 图片防盗链的解决办法

    在做图片展示部分的时候发现img标签src为图片url时无法显示,发现是这些图片来源网站为了防止图片盗链浪费资源所以不让外站访问。它们的识别机制是根据http请求里的header中的referer来判断来源网站,比如我在chrome开发者工具里查看值值值网站的图片,就能看到完整的request header。 Accept:   image/webp,*/*;q=0.8

  • 如何配置Apache 防止图片盗链

     独立建博的用户经常面对的一个问题就是服务器的流量问题,而站点文件被盗链是其中最为主要的部分。所谓盗链,是指其他网站直接链接我们网站上的文件,一般来说,盗链的对象大多为很耗带宽的大体积文件,如图片、视频等。这样造成的后果主要有:增加了服务器的流量负担,影响我们网站的访问速度。如果你用的是Apache服务器,那么使用.htaccess文件就可以很方便的进行设置,从而防止其他站点的

  • 关于某些网站的图片盗链功能,相关http 请求的解决方法

    其实 关键就是 header中 的 Referer 属性,可以通过firebug 查看头信息 并获得Referer,但有些情况下可能不能获取,就只能通过抓包的方法来获得了。 [java] view plain copy print? import java.io.BufferedInputStream;  import java.io.BufferedOu

  • 手机端APP防盗链配置问题

    1,一般情况都是网页端访问一个图片会带上这个网页的一个refer,然后云端判断refer的来源来开启防盗链的黑白名单。2,so问题来了手机端APP防盗链配置问题。怎么配置呢?首先手机端发请求抓包的话。都没有带refer所以无法判断来自于app内部的请求还是外部的请求。这样就比较难办了。配置黑白名单没有效果。需要抓包看下,app中的资源访问的referer是什么,然后再做针对性的防盗链配置;如果能够保

  • 【转】Apache下防止图片盗链(完整篇)

    转自 http://blogsdiy.org/2007-04/prevent-hotlinking/ 相信很多朋友都曾遇到过网站内文件特别是图片被盗链的情况。所谓盗链,是指对方网站直接链接您网站上的文件,而不是将其置于自己的服务器上,一般而 言,盗链的对象大多为较耗带宽的大体积文件,如图片、视频等,从某种意义上说,这事实上造成了让您为其访问流量买单:不仅您的服务器带宽被无任何回报...

  • 如何实现网站的防盗链?

    背景 A是网站站长,在A的网站的网页里有一些图片和音频视频的链接,这些静态资源都保留在阿里云对象存储OSS上。B是另一个网站的站长,B在未经A允许的情况下,偷偷使用A的网站的图片资源,放置在自己网站的网页中,通过这种方法盗取空间和流量。 在这样的情况下,第三方网站用户看到的是B的网站,网站用户不知道也不关心网站里的图片是来自于哪里。 由于OSS是按照使...

  • PHP防止盗链的方法

    防盗链的技术已经很普遍了,有些网站不喜欢自己的图片被别的网站直接复制使用,便使用了防盗链的技术,这样别人在直接复制使用网站图片时,图片便会按照程序的设定不显示或显示防盗链等字样。 使用了防盗链技术,不仅可以防止自己的图片被盗用,也可以节省自己站点下载图片的流量,觉得还是蛮不错的说,那么在PHP环境下应该如何防止盗链呢?在PHP环境下我们知道通常都是使用Apache服务器,那么主要看下Apache

  • 防止图片盗链和资源恶意下载

    这里首先要提一下,IIS是怎么怎么处理http请求的 当服务器接收到一个 Http请求的时候,IIS 首先需要决定如何去处理这个请求(服务器处理一个.htm页面和一个.aspx页面肯定是不一样)。那IIS依据什么去处理呢?―― 根据文件的后缀名。 服务器获取所请求的页面(也可以是文件,比如 jimmy.jpg)的后缀名以后,接下来会在服务器端寻找可以处理这类后缀名的应用程序,如果IIS找不到可...

  • 网站防止盗链的实现办法

    Global文件里有个Application_BeginRequest方法,在这个里面写 if(HttpContext.Current.Request.Url.AbsolutePathEndsWith(".jpg"))  判断访问的文件是否为jpg图片 HttpContext.Current.Request.UrlReferrer.Host  判断访问程序的地址 如果不为本站,则禁止访问

  • 主流浏览器图片反防盗链方法总结

    (点击上方公众号,可快速关注)作者:Mythsblog.mythsman.com/2018/04/20/1/前言还记得之前写的那个无聊的插件,前一段时间由于豆瓣读书增加了...

  • Apache和Nginx下禁止访问特定的目录或文件

    大家是否测试Apache做了目录禁止浏览后,目录下面的txt文件还是可以显示里面的内容的。 例如:http://www.domain.com/test/此访问会报403错误,但是如果test下有很多txt,你访问该txt时; 例如:http://www.domain.com/test/a.txt,此时a.txt里的内容会全部暴露在外面了(有时这个txt是很机密的文件),这样以来就不安全了。...

Global site tag (gtag.js) - Google Analytics