在C/C++代码中使用SSE等指令集的指令(1)介绍
我们知道,在C/C++代码中,可以插入汇编代码提高性能。现在的指令集有了很多的高级指令,如果我们希望使用这些高级指令来实现一些高效的算法,就可以在代码中嵌入汇编,使用SSE等高级指令,这是可行的,但是如果对汇编不太熟悉,不愿意使用汇编的人来说,其实也是可以的,这就是Compiler Intrinsics(http://msdn.microsoft.com/zh-cn/site/26td21ds)。
PS:下面的内容以Windows平台为主,对于Linux下,也有类似的方法。
(1)什么是Intrinsics
Intrinsics是对MMX、SSE等指令集的指令的一种封装,以函数的形式提供,使得程序员更容易编写和使用这些高级指令,在编译的时候,这些函数会被内联为汇编,不会产生函数调用的开销。在理解intrinsics指令之前,先理解intrinsics函数。
(3)#pragma intrinsic和#pragma function
#pragma intrinsic(function[,function][,function]...):表示后面的函数将进行intrinsic,替换为内部函数,去掉了函数调用的开销,注意:有些地方解释为内联,但是和内联并不完全相同,对于内联,可以指定任意函数为内联,但是此pragma intrinsic只能适用于编译器规定的一部分函数,不是所有函数都能使用,而且,inline关键字一般用于指定自定义的函数,intrinsic则是系统库函数的一部分。参考http://technet.microsoft.com/zh-cn/library/tzkfha43.aspx获取详细的说明。
下面分析这个例子:
- #include <math.h>
- void foo()
- {
- double var = cos(10);
- }
使用VS2010的32bit的command line编译:
cl /c test.c /FA
输出得到其汇编文件:
- ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
- TITLE C:\tempLab\test.c
- .686P
- .XMM
- include listing.inc
- .model flat
- INCLUDELIB LIBCMT
- INCLUDELIB OLDNAMES
- PUBLIC __real@4024000000000000
- PUBLIC _foo
- EXTRN _cos:PROC
- EXTRN __fltused:DWORD
- ; COMDAT __real@4024000000000000
- ; File c:\templab\test.c
- CONST SEGMENT
- __real@4024000000000000 DQ 04024000000000000r ; 10
- ; Function compile flags: /Odtp
- CONST ENDS
- _TEXT SEGMENT
- _var$ = -8 ; size = 8
- _foo PROC
- ; Line 3
- push ebp
- mov ebp, esp
- sub esp, 8
- ; Line 4
- sub esp, 8
- fld QWORD PTR __real@4024000000000000
- fstp QWORD PTR [esp]
- call _cos
- add esp, 8
- fstp QWORD PTR _var$[ebp]
- ; Line 5
- mov esp, ebp
- pop ebp
- ret 0
- _foo ENDP
- _TEXT ENDS
- END
可以看到,这里调用了call _cos函数进行运算,下面代码修改如下:
- #include <math.h>
- #pragma intrinsic(cos)
- void foo()
- {
- double var = cos(10);
- }
同样的命令编译,得到汇编如下:
- ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
- TITLE C:\tempLab\test.c
- .686P
- .XMM
- include listing.inc
- .model flat
- INCLUDELIB LIBCMT
- INCLUDELIB OLDNAMES
- PUBLIC __real@4024000000000000
- PUBLIC _foo
- EXTRN __fltused:DWORD
- EXTRN __CIcos:PROC
- ; COMDAT __real@4024000000000000
- ; File c:\templab\test.c
- CONST SEGMENT
- __real@4024000000000000 DQ 04024000000000000r ; 10
- ; Function compile flags: /Odtp
- CONST ENDS
- _TEXT SEGMENT
- _var$ = -8 ; size = 8
- _foo PROC
- ; Line 4
- push ebp
- mov ebp, esp
- sub esp, 8
- ; Line 5
- fld QWORD PTR __real@4024000000000000
- call __CIcos
- fstp QWORD PTR _var$[ebp]
- ; Line 6
- mov esp, ebp
- pop ebp
- ret 0
- _foo ENDP
- _TEXT ENDS
- END
对比之后,它们的主要区别的代码段如下:
- sub esp, 8
- fld QWORD PTR __real@4024000000000000
- fstp QWORD PTR [esp]
- call _cos
- add esp, 8
- fld QWORD PTR __real@4024000000000000
- call __CIcos
显然,使用了Intrinsics之后的cos函数的指令少了很多,其调用的内部函数是_CIcos(http://msdn.microsoft.com/zh-cn/library/ff770589.aspx),此函数会计算对栈顶的元素直接进行cos运算,所以节省了很多函数调用参数传递等的指令。
仍然参考MSDN(http://technet.microsoft.com/zh-cn/library/tzkfha43.aspx)可以看到其中一段话:
The floating-point functions listed below do not have true intrinsic forms. Instead they have versions that pass arguments directly to the floating-point chip rather than pushing them onto the program stack.
当然,这是描述其中一部分Intrinsics函数的,Intrinsics也有不同的方式进行优化/内联,具体参考MSDN查询哪些函数可以使用Intrinsics以及是如何工作的(http://msdn.microsoft.com/zh-cn/site/26td21ds)。
#pragma function:使用格式和intrinsics一样,pragma function用于指定函数不进行intrinsics操作,也就是不生成内部函数。
最后,要知道的一个内容是一个相关的编译选项:/Oi
http://technet.microsoft.com/zh-cn/library/f99tchzc.aspx
/Oi 仅作为对编译器的请求,用于将某些函数调用替换为内部函数;为产生更好的性能,编译器可能会调用函数(而不会将该函数调用替换为内部函数)。
简单的理解,就是告诉编译器尽量使用intrinsics版本的调用,当然,最终的实际调用依赖于编译器的判断。
也可以参考wiki中(http://en.wikipedia.org/wiki/Intrinsic_function)关于intrinsic functions来帮助理解其作用。简单来说,可以理解为编译器的“内置函数”,编译器会根据情况进行一些优化。
(4)指令集相关的intrinsics介绍
上面介绍的是pragma对intrinsic函数的使用,其中介绍了cos,还有很多类似的“内置函数版本”。有时候将上面的这些称之为”intrinsics函数“,除此之外,intrinsics更广泛的使用是指令集的封装,能直接映射到高级指令集,从而使得程序员可以以函数调用的方式来实现汇编能达到的功能,编译器会生成为对应的SSE等指令集汇编。
1. 如何使用这类函数
在windows上,包含#include <**mmintrin.h>头文件即可(不同的指令集扩展的函数可能前缀不一样),也可以直接包含#include <intrin.h>(这里面会根据使用环境判断使用ADM的一些兼容扩展)。
2. 关于数据类型
这些和指令集相关的函数,一般都有自己的数据类型,不能使用一般的数据类型传递进行计算,一般来说,MMX指令是__m64(http://msdn.microsoft.com/zh-cn/library/08x3t697(v=VS.90).aspx)类型的数据,SSE是__m128类型的数据等等。
3. 函数名:
这类函数名一般以__m开头。函数名称和指令名称有一定的关系。
4. 加法实例:
下面使用SSE指令集进行加法运算,一条指令对四个浮点数进行运算:
- #include <stdio.h>
- #include <intrin.h>
- int main(int argc, char* argv[])
- {
- __m128 a;
- __m128 b;
- a = _mm_set_ps(1,2,3,4); // Assign value to a
- b = _mm_set_ps(1,2,3,4); // Assign value to a
- __m128 c = _mm_add_ps(a, b); // c = a + b
- printf("0: %lf\n", c.m128_f32[0]);
- printf("1: %lf\n", c.m128_f32[1]);
- printf("2: %lf\n", c.m128_f32[2]);
- printf("3: %lf\n", c.m128_f32[3]);
- return 0;
- }
从代码看,好像很复杂,但是生成的汇编的效率会比较高。一条指令就完成了四个浮点数的加法,其运行结果如下:
(5)总结:
1. Intrinsics函数:能提高性能,会增大生成代码的大小,是编译器的”内置函数“。
2. Intrinsics对指令的封装函数:直接映射到汇编指令,能简化汇编代码的编写,另外,隐藏了寄存器分配和调度等。由于涉及到的数据类型、函数等内容较多,这里只是一个简单的介绍。
在C/C++代码中使用SSE等指令集的指令(1)介绍相关推荐
- c/c++ 代码中使用sse指令集加速
使用SSE指令,首先要了解这一类用于进行初始化加载数据以及将暂存器的数据保存到内存相关的指令, 我们知道,大多数SSE指令是使用的xmm0到xmm8的暂存器,那么使用之前,就需要将数据从内存加载到这些 ...
- js获取html代码中所有图片地址
/** * JS获取html代码中所有的图片地址 * @param htmlstr * @returns imgsrcArr 数组 */ function getimgsrc(htmlstr) { v ...
- 实验四:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
贺邦+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验目的: 使用库函数 ...
- 如何解决代码中if…else 过多的问题
前言 if...else 是所有高级编程语言都有的必备功能.但现实中的代码往往存在着过多的 if...else.虽然 if...else 是必须的,但滥用 if...else 会对代码的可读性.可维护 ...
- 不要依赖代码中的异常
因为异常大大地降低性能,所以您不应该将它们用作控制正常程序流程的方式.如果有可能检测到代码中可能导致异常的状态,请执行这种操作.不要在处理该状态之前捕获异常本身.常见的方案包括:检查 null,分配给 ...
- 在后台代码中引入XAML的方法
本文将介绍三种方法用于在后台代码中动态加载XAML,其中有两种方法是加载已存在的XAML文件,一种方法是将包含XAML代码的字符串转换为WPF的对象. 这些是我在编写RegeX时获得的经验,它们将会给 ...
- php代码中使用换行及(\n或\r\n和br)的应用
浏览器识别不了\n或\r\n,这两个换行符是文本换行符,文本文件有效;假设须要将结果输出到浏览器或打印到显示器,代码中使用br;假设仅仅是在源码中换行.则使用\n或\r\n,感兴趣的朋友能够了解下,也 ...
- 测试nginx网站代码_在40行以下代码中使用NGINX进行A / B测试
测试nginx网站代码 by Nitish Phanse 由Nitish Phanse 在40行以下代码中使用NGINX进行A / B测试 (A/B testing with NGINX in und ...
- eclipse 代码中突然出现特殊字符
在写代码的时候,不知道点到了 eclipse 的哪个属性,代码中就出现了一些特殊字符,也不能删除. 请问,在 eclipse 中该怎么设置,才能将这些字符去掉. 如下图所示: 解决方法: 选择Wind ...
最新文章
- Python之函数的收集参数和分配参数用法(‘*’ 和 ‘**’)
- Apache Kafka消息格式的演变(0.7.x~0.10.x)
- pandas数据存储于读取
- python全栈开发_day10_函数的实参和形参
- VSCode设置ESLint语法检查
- 判断控件是否绑定了数据集的方法
- AndroidStudio_android通过自定义来实现倒计时的AlertDialog---Android原生开发工作笔记245
- ES6学习(十一)—Class 的基本语法和继承
- Ubuntu 14.04 英文版安装中文输入法
- android web canvas,HTML5 - Canvas无法在Android WebView的第一次加载时渲染
- JQuery Mobile中特有事件和方法
- iOS底层探索之多线程(十六)——锁分析(NSLock、NSCondtion、NSRecursiveLock、NSCondition)
- USB 大容量存储设备的开发
- 微信第三方登陆,无需注册一键登录,获取用户信息,PHP实现方法.
- grafana快速搭建数据平台
- 摩拜单车深度产品体验报告
- 洛谷P1273 有线电视网 题解
- “d3dx9.h”: No such file or directory 解决办法
- 什么是配置文件 java_java配置文件是什么
- 品悟C 抛弃C程序设计中的谬误与恶习 pdf
热门文章
- C# ManualResetEvent的理解和用法
- net.sf.fmj.media.cdp.civil.CaptureDevicePlugger addCaptureDevices解决方法
- /dev/zero和/dev/null的区别
- FrameBuffer研究
- python setdefault函数_python 字典 setdefault()和get()方法比较详解
- aes256 加密后的长度_视频会议Zoom 5.0版本重大更新,增强加密功能提供更多安全选项...
- oracle处理回车换行符
- ionic android 版本号,ionic android 版本release 和 签名(示例代码)
- 第39级台阶(暴力搜索)
- mysql 内存表使用教程_MySQL的内存表的基础学习教程