GCC中SIMD指令的应用方法

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

X86的SIMD指令 ...simd instrucitons in X86

IA-32 Intel体系结构的指令主要分为以下几类 [1]

  • 通用
  • x87 FPU
  • MMX技术
  • SSE/SSE2/SSE3扩展

MMX/SSE类扩展引入了SIMD(单指令多数据)的执行模式,可用于加速多媒体应用。 下面简要介绍一下这些指令的执行环境和特征。

  • 8个32位通用寄存器可为各个SIMD扩展所使用;
  • MMX:8个64位MMX寄存器(mm0 - mm7),也可为各SSE扩展所使用;
    • 数据为整数,最多支持两个32位
    • 运算中没有寄存器能够进行溢出指示
  • SSE:8个128位xmm寄存器,MXSCR寄存器,EFLAGS寄存器
    • 支持单精度浮点
    • MXSCR含有rounding, overflow标志
    • 支持64位SIMD整数
  • SSE2:执行环境同sse
    • 双精度浮点
    • 128位整数
    • 双—单精度转换
  • SSE3:与Inte Prescott处理器一同发布不久,共13条指令
    • 主要增强了视频解码、3D图形优化和超线程性能

MMX技术出现最早,目前几乎所有的X86处理器都提供支持,包括嵌入式X86, 所以下面的讨论主要基于MMX,但方法完全适用于SSEn, 包括像AMD的3D Now等其它SIMD扩展。

MMX指令又分为以下几种:

  • 数据传送:movd, movq
  • 数据转换:packsswb, packssdw, packuswb, punpckhbw, punpckhwd, punpckhdq, punpcklbw, punpcklwd, punpckldq
  • 并行算术:paddb, paddw, paddd, paddsb, paddsw, paddusb, paddusw, psubb, psubw, psubd, psubsb, psubsw, psubusb, psubusb, psubusw, pmulhw, pmullw, pmaddwd
  • 并行比较:pcmpeqb, pcmpeqw, pcmpeqd, pcmpgtb, pcmpgtw, pcmpgtd
  • 并行逻辑:pand, pandn, por, pxor
  • 移位与旋转:psllw, pslld, psllq, psrlw, psrld, psrlq, psraw, psrad
  • 状态管理:emms

这些指令除了需要注意功能外,还需要注意处理的数据类型。以上内容为背景介绍,细节请参考手册。





性能优化 ...Performance Optimization

当使用C/C++完成了一个嵌入式应用的所有功能,性能问题常摆在面前, 这时可以使用profile工具(如gprof)找出产生瓶颈的函数, 将这些函数使用汇编彻底重写, 例如MPEG-4编解码器xvid项目 [4]就使用了这种方法, 而且针对不同处理器/指令集分别给出了不同的优化, 正是如此该项目无论功能、还是性能均为一流, 显然这是深度优化的目标所在。

在使用流水线、VLIW以及SIMD的体系结构(比如某些DSP)上, 整个函数的手工优化可以带来几倍到几十倍的性能提升。 不过,性能允许,对于函数内关键部分使用一些特定的实现, 既突出重点提高性能,又可以尽多地利用C/C++的高级特征, 相对缩短开发周期。 下面给出使用GCC时,应用MMX指令的几种混合编程方法:

  • Intel C/C++ 编译器intrinsics
  • GCC builtin操作
  • 嵌入汇编asm construct




Intel C/C++ 编译器intrinsics ...Intel C/C++ Compiler Intrinsics

查看IA-32 Intel指令集手册 [2]时, 部分指令的解释中会有一项“Intel C/C++ Compiler Intrinsic Equivalent”, 会指出该指令对等的intrinsic。 intrinsic在C/C++程序中的语法是以函数形式出现, 编译时可以直接翻译为一条MMX指令(复合情况会生成最直接的几条), 换言之,如果不使用intrinsic,可能需要多条C/C++语句完成, 而编译器却并不能保证将这几条语句能够生成这条最高效的MMX指令。 并不是每条MMX指令都有对等的intrinsic, 手册的附录中列出了所有的, 它们分为简单型(simple)和复合型(composite)两种, 每个简单型的就是对应一条指令,而复合型则对应多条指令。

GCC支持Intel C/C++ Compiler Intrinsics。用法如下示例:


      #include <stdio.h>#include <xmmintrin.h> /*一定需要包括此头文件*//*gcc -Wall -march=pentium4 -mmmx -o ins  mmx_ins.c*/int main(int argc,char *argv[]){ /*使用MMX做以下向量的点积*/short in1[] = {1, 2, 3, 4};short in2[] = {2, 3, 4, 5};int out1;int out2;__m64 m1;    /* MMX支持64位整数的mm寄存器 */__m64 m2;    /* MMX操作需要使用mm寄存器 */__m128 m128; /* for SSEn only*//*每次往mm寄存器装入两个short型的数,注意是两个*/m1 = _mm_cvtsi32_si64(((int*)in1)[0]);m2 = _mm_cvtsi32_si64(((int*)in2)[0]); /*一条指令进行4个16位整数的乘加*//*生成两个32位整数*/m2  = _mm_madd_pi16(m1, m2); /*将低32位整数放入通用寄存器*/out1 =  _mm_cvtsi64_si32(m2);/*将高32位整数右移后,放入通用寄存器*/m2  = _mm_slli_pi32(m2, 32);out2 =  _mm_cvtsi64_si32(m2);/*清除MMX状态*/_mm_empty();/*将两个32位数相加,结果为8*/out1 += out2;printf("a: %d/n", out1);return(0);}

几点说明:

  • 即使你不是P4平台,编译时也请使用以下选项,
    /*gcc -Wall -march=pentium4 -mmmx -o ins mmx_ins.c*/
    否则,会出现如下类似信息:
    ...xmmintrin.h:34:3: #error "SSE instruction set not enabled"
  • 最终结果实际并没有求得四对乘积的和,只是前两对的, instrinsic _mm_cvtsi32_si64只向mm寄存器放入了低32位,高32位为零, 但mmx有指令movq可以做到64位的数据传送,intrinsic没有对应, 这也说明并不是所有的指令有等价的intrinsic。
  • 当计算的向量为两对0x8000, 0x8000时,即(-2^15)*(-2^15) + (-2^15)*(-2^15) , 结果应该为 2^31,但计算出来的值是 -2^31, 因为发生了溢出,可程序无从知道。 这是使用MMX时,应特别注意的,计算溢出没有任何标志位指示,一个极大的值变为极小,SSE对此做了改善。
  • 程序不再使用MMX之时,注意使用emms指令清除MMX状态。




使用built-in操作 ...GCC built-in Operation

什么是built-in操作?就是对待MMX操作数,就如int, float等基本数据类型一般, 有相应定义的操作,如加(+)、减(-),或者数据类型之间的转换。 详细内容参考GNU GCC Manual[5] Extensions to the C Language Family4#4Built-in Functions4#4 X86 Built-in Functions一节。

一些MMX指令有其相应的built-in操作, 下面一段代码为例:


      #include <stdio.h>/*无需特别的头文件,built-in嘛*//* gcc -Wall  -o bins  builtinmmx.c*//*定义了一个vector数据类型,hi表示16位,4表示4个*/typedef int v4hi __attribute__ ((mode(V4HI)));/*定义了2个32位的vector类型,si表示32位*/typedef int v2si __attribute__ ((mode(V2SI)));int main(int argc,char *argv[]){ short pa[4] = {0x8000, 0x8000, 1, -1};short pb[4] = {0x8000, 0x7FFF, -1, -2};v4hi va, vb;v4hi vsum;va = ((v4hi*)pa)[0];vb = ((v4hi*)pb)[0];/* 4个16位进行饱和加 *///vsum = __builtin_ia32_paddsw(va, vb);/* 4个16位还可以直接进行加法,但不同于两个long long相加 */vsum =  va + vb;/*vector的输出还需要强制转换为long long*/printf("...with MMX instructions...to compute vec_add: %llx /n", (long long)vsum);//结果1:0xfffd0000ffff8000//结果2:0xfffd0000ffff0000return(0);}

几点说明:

  • 是的,这里built-in vector及其操作,随着GCC的发展正在加强。如果需要使用以上范例,应使用GCC 3.4以上版本;
  • 使用builtin函数时,与intrinsic相似;但本质却是不同,这里两个向量使用‘+’操作就说明了vector也如其它数据类型一样,编译器直接支持,只不过这里的加法就是指四个单元数分别相加,低位单元的进位不会影响相邻高位单元的数据;
  • vector还可以强制转换为通用数据。




嵌入汇编 ...Inline asm

GCC一开始就允许C代码中嵌入asm指令,并不只是针对MMX指令, 不过对于MMX技术,显然也是一个很好的利用方法, 详细的语法请参考GNU GCC手册 [5], 或者GCC: The Complete Reference [6]''Inline Assembly''一节。 如下是一个点积的例子:


      #include <stdio.h>/** GCC -o ins  inlinemmx.c **/int main(int argc,char *argv[]){ int i;int result;short a[] = {1, 2, 3, 4, 5, 6, 7, 8};short b[] = {1, 1, 1, 1, 1, 1, 1, 1};printf("...with MMX instructions.../n");/*首先,将点积合累积寄存器清零,实际缺省就为0?*/asm("pandn %%mm5,%%mm5;"::);/*读入a, b,每四对数相乘后分两组相加,形成两组和*//*这里的循环控制是C在做*/for(i = 0; i < sizeof(a)/sizeof(short); i += 4){asm("movq %0,%%mm0;/movq %1,%%mm1;/pmaddwd %%mm1,%%mm0;/paddd %%mm0,%%mm5; #相乘后相加 ":: "m" (a[i]), "m" (b[i]));}/*将两组和分离,并相加*/asm("movq %%mm5, %%mm0;/psrlq $32,%%mm5;/paddd %%mm0, %%mm5;/movd %%mm5,%0;/emms":"=r" (result):);printf("result: 0x%x/n", result);//这里结果为0x24return(0);}

几点说明:

  • 这里是典型的在函数中C和汇编混合编程;
  • 注意汇编指令中操作数的顺序;
  • 这里可以直接使用movq等没有intrinsics/built-in对应的指令;
  • 注意在asm指令序列中间不要加杂注释,可能导致生成的代码不正确。




MMX实用一例:合成滤波器 ...Synthesis Filter in X86 SIMD INSTRUCTIONS

下面是合成滤波器(Synthesis Filter)的一个优化过程, 合成滤波器在语音编解码中有广泛应用, 运行时也占用了整个算法中较高比例的时间。


      for (i = 0; i < lg; i++){s = L_mult(x[i], a[0]);/*L_mult是相乘后左移*/for (j = 1; j <= M; j++){/*M这里固定为10*/s = L_msu(s, a[j], yy[-j]);/*L_msu是乘减后左移操作*/}s = L_shl(s, 3); /*左移三位*/*yy++ = g729round(s);}#endif

上面的代码,因为内存循环为10,可以考虑展开,并统一操作为乘加指令。


      /*为了使用乘加操作,需要调整10个系数的顺序*/for(i = 0; i < M; i++)ta[i] = -a[M - i];ta[11] = 0;ta[10] = a[0];for (i = 0; i < lg; i++){*yy = x[i];yy[1] = 0;s = L_mac(s, ta[11], yy[1]);s = L_mac(s, ta[10], yy[0]);s = L_mac(s, ta[9], yy[-1]);s = L_mac(s, ta[8], yy[-2]);s = L_mac(s, ta[7], yy[-3]);s = L_mac(s, ta[6], yy[-4]);s = L_mac(s, ta[5], yy[-5]);s = L_mac(s, ta[4], yy[-6]);s = L_mac(s, ta[3], yy[-7]);s = L_mac(s, ta[2], yy[-8]);s = L_mac(s, ta[1], yy[-9]);s = L_mac(s, ta[0], yy[-10]);s = L_shl(s, 3);*yy++ = g729round(s);}

以上循环内核正好可以将MMX的8个寄存器全部利用。


      /*为了使用乘加操作,需要调整10个系数的顺序*/for(i = 0; i < M; i++)ta[i] = -a[M - i];ta[11] = 0;ta[10] = a[0];/*11个系数分别放入3个MMX寄存器,0作填充*/asm("movq %0,%%mm0;/movq %1,%%mm1;/movq %2,%%mm2"/:/: "m" (ta[0]), "m" (ta[4]), "m"(ta[8]));/*利用MMX技术进行滤波器核心操作*/for (i = 0; i < lg; i++){*yy = x[i];yy[1] = 0;asm("pandn %%mm6,%%mm6;/movq %1,%%mm3;/movq %2,%%mm4;/movq %3,%%mm5;/pmaddwd %%mm0,%%mm3;/pmaddwd %%mm1,%%mm4;/pmaddwd %%mm2,%%mm5;/paddd %%mm3, %%mm6;/paddd %%mm4, %%mm6;/paddd %%mm5, %%mm6;/movq  %%mm6, %%mm7;/psrlq $32, %%mm6;/paddd %%mm7, %%mm6;/movd %%mm6,%0;/emms"::"r"(s), "m" (yy[-10]), "m" (yy[-6]), "m"(yy[-2]));/*因为指令结果饱和属性的限制,s还没有左移,所以下面多做一位饱和左移*/s = L_shl(s, 4);*yy++ = g729round(s);}

几点说明:

  • 注意:以上嵌入的汇编代码输出结果s放在了输入处,属于实践中的个案;
  • MMX没有乘左移之类的DSP指令,甚至还没有加饱和之类的操作,SSE中有一定增强;
  • 以上操作,理论上存在溢出可能,所以最后使用原有的饱和左移操作,减少了一定风险;
  • 上面的部分代码操作显然允许并行,这在VLIW系统中十分有用;
  • 这已经形成了该滤波器全面优化的核心。




总结 ...Conclusion

如果愿意尽多地利用SIMD技术,可能需要更多地使用汇编级的编码, 不过也有一些高级语言和汇编的混合编程技术能够帮助你, 它们有的提高性能更大一些, 有的形式上更优雅些,本质上效率也不错, 都不失好的方法,建议尝试。

正是如此,一方面CPU上支持越来越多的SIMD指令集扩展, 另一方面GCC也正在加紧支持这些扩展的易用,对,正在, 碰到一些问题,先想办法绕过去, 这里使用GCC 3.4.1,根据经验效果还是不错的。





关于文档

GCC中SIMD指令的应用方法

This document was generated using the LaTeX2HTML translator Version 2002 (1.62)

Copyright ® 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright ®, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were: latex2html -iso_language CN -html_version 4.0,unicode -address '®2004 CoreUp Designs' -local_icons -split 0 -nonavigation gccsimd

The translation was initiated by on 2004-12-13





参考资料

  1. Intel: IA-32 Intel Architechture Software Developer's Manual, Volume 1: Basic Architecture(2002)
  2. Intel: IA-32 Intel Architechture Software Developer's Manual, Volume 2: Instruction Set Reference(2003)
  3. Intel: IA-32 Intel Architechture Software Developer's Manual, Volume 3: System Programming Guide(2003)
  4. XviD.org,http://www.xvid.org/(up-to-date)
  5. GNU, GCC online documentation, http://www.gnu.org/software/GCC/onlinedocs/(up-to-date)
  6. Authur Griffith, GCC: The Complete Referencea, McGraw Hill(2002)




关于作者

 

钱浙滨,1999年从上海交通大学图像处理与模式识别研究所获得博士学位, 曾参与完成计算机视觉、正规语言和移动通信等方面的研发工作; 目前他和他的团队主要从事DSP系统开发,特别是多媒体编解码算法的性能优化, 以及相关的Linux嵌入式应用; 他们也提供WLAN相关的技术咨询, 欢迎访问http://embeddedcore.com进行交流。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454169.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用angular4和asp.net core 2 web api做个练习项目(二), 这部分都是angular

上一篇: http://www.cnblogs.com/cgzl/p/7755801.html 完成client.service.ts: import { Injectable } from angular/core; import { Http, Headers } from angular/http; import { Observable } from rxjs/Observable; import { ErrorHandler } from angular/core; import rxj…

leelen可视对讲怎么接线_楼宇对讲系统怎么布线 楼宇对讲系统布线方式【介绍】...

随着智能小区规模不断增加&#xff0c;楼宇可视对讲系统应用越来越广泛&#xff0c;因而视频信号的传输方式与布线设计显得越来越重要。视频信号与数据和音频信号不同&#xff0c;可行的一种传输方式为视频信号基带传输&#xff0c;下面小编就简要介绍一下这种传输方式和布线方…

路由汇总实例

5.2.2.2 路由汇总策略 之前提到过&#xff0c;在网络管理员计划好子网选择并进行预期地路由汇总时&#xff0c;手动路由汇总工作能取得最佳效果。例如&#xff0c;之前的例子设定好了一个考虑周全的计划&#xff0c;管理员只使用远离Yosemite路由器并以10.2开头的子网。这个规定…

《操作系统》OS学习(五):连续内存分配 内存碎片、动态分配、碎片整理、伙伴系统

内存碎片 在没有其他方式辅助的情况下&#xff0c;我们分配给一个进程的内存是连续的。在分配时候我们需要有动态分配与碎片处理。如何理解呢&#xff1f;就是每个进程需要一块内存&#xff0c;我们要选取合适的位置的内存分配给它。当有的进程先结束了内存还给操作系统&#…

世界之窗浏览器删除文本框信息_文本框——Excel里的便利贴

工作表里面的单元格应该是足够我们来记录数据和信息了。但是文本框这个功能在工作表中还是存在&#xff0c;可以理解为便利贴功能。插入文本框1.点击“插入”选项卡。2.然后点击“文本框”。3.在下拉菜单里面&#xff0c;有两种可供选择&#xff1a;横排文本框和垂直文本框。在…

RHEL 5服务篇—常用网络配置命令

常用网络配置命令 在“Linux系统管理”的文章中&#xff0c;大家已经学习了Linux系统的基本管理命令和技巧&#xff0c;为了进一步学习Linux网络服务打下了良好的基础。所以我作者以后将陆续推出Linux网络服务的相关文章。希望大家能给与我大大的支持。 今天我们就来学习一下…

清华大学《操作系统》(六):非连续内存分配 段式、页式、段页式存储管理

背景 连续内存分配给内存分配带来了很多不便&#xff0c;可能所有空闲片区大小都无法满足需求大小&#xff0c;这个分配就会失败。基于这种现状&#xff0c;就有了非连续内存分配的需求。非连续分配成功的几率更高&#xff0c;但也面对更多的问题&#xff0c;比如分配时是不是…

C语言第三次博客作业---单层循环结构

一、PTA实验作业。 题目1 1.实验代码 int n,i; double height1,height2;//1为输入身高&#xff0c;2为输出身高。 char sex; //1<height1<3; //N<1; scanf("%d",&n); while(n--){getchar();scanf("%c%lf",&sex,&height1);switch(sex)…

清华大学《操作系统》(七):虚拟存储、覆盖、交换

接下来几节都是对虚拟存储的讲解。虚拟存储是非连续存储管理的扩展。通过将内存中的数据暂存到外存的方式&#xff0c;为进程提供更大的内存空间。虚拟存储出现的主要原因是因为程序规模的增长速度远远大于存储器容量的增长速度&#xff0c;导致内存空间不够用。其实针对内存空…

遵义大数据中心项目工程概况_市委书记张新文到曹州云都大数据中心等项目现场调研建设情况...

4月25日&#xff0c;市委书记张新文到曹县调研重点项目建设情况&#xff0c;研究推进措施。市委常委、秘书长任仲义参加活动。张新文首先来到曹州云都大数据中心项目建设现场&#xff0c;查看项目推进情况。曹州云都大数据中心&#xff0c;是涵盖云计算区、研发办公区、公寓生活…

linux 可执行文件的分析(gcc GUN BUILEIN)

1、GCC The History of GCC 1984年&#xff0c;Richard Stallman发起了自由软件运动&#xff0c;GNU (Gnus Not Unix)项目应运而生&#xff0c;3年后&#xff0c;最初版的GCC横空出世&#xff0c;成为第一款可移植、可优化、支持ANSI C的开源C编译器。GCC最初的全名是GNU C Com…

Cassandra 的数据存储结构——本质是SortedMapRowKey, SortedMapColumnKey, ColumnValue

Cassandra 的数据存储结构 Cassandra 的数据模型是基于列族&#xff08;Column Family&#xff09;的四维或五维模型。它借鉴了 Amazon 的 Dynamo 和 Googles BigTable 的数据结构和功能特点&#xff0c;采用 Memtable 和 SSTable 的方式进行存储。在 Cassandra 写入数据之前&a…

清华大学《操作系统》(八):置换算法

功能&#xff1a;置换算法是指当出现缺页异常时&#xff0c;需要调入新页面而内存已满时&#xff0c;置换算法选择被置换的物理页面。 设计目标&#xff1a; 尽可能减少页面的调入调出次数&#xff1b;把未来不再访问或短期内不访问的页面调出。 页面锁定&#xff1a; 了解具…

烂泥:通过vsphere给esxi添加本地硬盘

公司ESXi服务器的硬盘空间不够使用&#xff0c;现在新加了一块硬盘在ESxi服务器上。在服务器上添加完硬盘后&#xff0c;在Vsphere上是看不到新加硬盘的。 下面我们来通过虚拟机模拟该情况&#xff0c;先添加一块硬盘。如下图&#xff1a; 在Esxi添加完硬盘后&#xff0c;现在通…

清华大学《操作系统》(九):进程和线程

进程 定义&#xff1a; 进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行的过程。 组成&#xff1a; 代码数据状态寄存器&#xff08;正在运行的一个程序的所有状态信息&#xff09;&#xff1a;CPU状态CP0、指令指针IP通用寄存器&#xff1a;AX、BX、CX…

开始Flask项目

1.新建Flask项目。2.设置调试模式。3.理解Flask项目主程序。4.使用装饰器&#xff0c;设置路径与函数之间的关系。5.使用Flask中render_template&#xff0c;用不同的路径&#xff0c;返回首页、登录员、注册页。6.用视图函数反转得到URL&#xff0c;{{url_for(‘login’)}}&am…

烂泥:mysql数据库使用的基本命令

1、连接数据库的格式 mysql -h IP -u用户名 -p密码; 1.1连接远程数据库 mysql -h 192.168.1.214 -uroot -p123456 也可写成&#xff1a; mysql -h 192.168.1.214 -u root -p 123456 1.2连接本地数据库 mysql -uroot -p123456 也可写成&#xff1a; mysql -u root -p 123456 2、…

《操作系统》OS学习(十):进程控制

进程切换&#xff08;上下文切换&#xff09;&#xff1a; 定义&#xff1a;暂停当前运行进程&#xff0c;从运行状态变成其他状态&#xff0c;调度另一个进程从就绪状态变成运行状态要求&#xff1a;切换前&#xff0c;保存进程上下文&#xff1b;切换后&#xff0c;恢复进程…

GCC 命令选项使用详解

GCC 命令行详解[转帖] 1、gcc包含的c/c编译器 gcc、cc、c、g gcc和cc是一样的&#xff0c;c和g是一样的&#xff0c;一般c程序就用gcc编译&#xff0c;c程序就用g编译 2、gcc的基本用法 gcc test.c这样将编译出一个名为a.out的程序 gcc test.c -o test这样将编译出一个名为t…

mvn 打包_Spark源码打包编译的过程

前言上篇文章介绍了下 安装sbt环境 启动scala项目安装SBT环境运行Scala项目为什么要弄这个 因为我本来是想对spark源码编译部署spark是用scala语言编译的spark源码https://gitee.com/pingfanrenbiji/sparkspark提供的编译方式编译的前提是将所有的依赖包都下载下来而资源包管理…