大多数的函数是在库中,Intrinsic Function却内嵌在编译器中(built in to the compiler)。
1. Intrinsic Function
Intrinsic Function作为内联函数,直接在调用的地方插入代码,即避免了函数调用的额外开销,又能够使用比较高效的机器指令对该函数进行优化。优化器(Optimizer)内置的一些Intrinsic Function行为信息,可以对Intrinsic进行一些不适用于内联汇编的优化,所以通常来说Intrinsic Function要比等效的内联汇编(inline assembly)代码快。优化器能够根据不同的上下文环境对Intrinsic Function进行调整,例如:以不同的指令展开Intrinsic Function,将buffer存放在合适的寄存器等。
使用 Intrinsic Function对代码的移植性会有一定的影响,这是由于有些Intrinsic Function只适用于Visual C++,在其他编译器上是不适用的;更有些Intrinsic Function面向的是特定的CPU架构,不是全平台通用的。上面提到的这些因素对使用Intrinsic Function代码的移植性有一些不好的影响,但是和内联汇编相比,移植含有Intrinsic Function的代码无疑是方便了很多。另外,64位平台已经不再支持内联汇编。
2. SSE Intrinsic
VS和GCC都支持SSE指令的Intrinsic,SSE有多个不同的版本,其对应的Intrinsic也包含在不同的头文件中,如果确定只使用某个版本的SSE指令则只包含相应的头文件即可。
![]()
引用自:http://www.cnblogs.com/zyl910/archive/2012/02/28/vs_intrin_table.html
例如,要使用SSE3,则
#include <tmmintrin.h>
如果不关心使用那个版本的SSE指令,则可以包含所有
#include <intrin.h>
2.1 数据类型
Intrinsic使用的数据类型和其寄存器是想对应,有
* 64位 MMX指令集使用
* 128位 SSE指令集使用
* 256位 AVX指令集使用
甚至AVX-512指令集有512位的寄存器,那么相对应Intrinsic的数据也就有512位。
具体的数据类型及其说明如下:
1. __m64 64位对应的数据类型,该类型仅能供MMX指令集使用。由于MMX指令集也能使用SSE指令集的128位寄存器,故该数据类型使用的情况较少。
2. __m128 / __m128i / __m128d 这三种数据类型都是128位的数据类型。由于SSE指令集即能操作整型,又能操作浮点型(单精度和双精度),这三种数据类型根据所带后缀的不同代表不同类型的操作数。__m128是单精度浮点数,__m128i是整型,__m128d是双精度浮点数。
256和512的数据类型和128位的类似,只是存放的个数不同,这里不再赘述。
知道了各种数据类型的长度以及其代码的意义,那么它的表现形式到底是怎么样的呢?看下图
__m128i yy;
yy是__m128i型,从上图可以看出__m128i是一个联合体(union),根据不同成员包含不同的数据类型。看其具体的成员包含了8位、16位、32位和64位的有符号/无符号整数(这里__m128i是整型,故只有整型的成员,浮点数的使用__m128)。而每个成员都是一个数组,数组中填充着相应的数据,并且根据数据长度的不同数组的长度也不同(数组长度 = 128 / 每个数据的长度(位))。在使用的时候一定要特别的注意要操作数据的类型,也就是数据的长度,例如上图同一个变量yy当作4个32位有符号整型使用时其数据是:0,0,1024,1024;但是当做64位有符号整型时其数据为:0,4398046512128,大大的不同。
在MSVC下可以使用yy.m128i_i32[0]
取出第一个32位整型数据,原生的Intrinsic函数是没有提供该功能的,这是在MSVC的扩展,比较像Microsoft的风格,使用及其的方便但是效率很差,所以这种方法在GCC/Clang下面是不可用的。在MSVC下面可以根据需要使用不使用这种抽取数据的方法,但是这种功能在调试代码时是非常方便的,如上图可以很容易的看出128位的数据在不同数据类型下其值的不同。
2.2 Intrinsic 函数的命名
Intrinsic函数的命名也是有一定的规律的,一个Intrinsic通常由3部分构成,这个三个部分的具体含义如下:
1. 第一部分为前缀_mm,表示是SSE指令集对应的Intrinsic函数。_mm256或_mm512是AVX,AVX-512指令集