intel SSE指令集大全--c++ 指令集函数基础和数据加载

前言

c++图像算法修行之路

intel指令集官网

intel SSE指令集大全(128位寄存器)



1 c++内联指令集函数Intrinsic基础

128位寄存器即一次只能处理128位的数据:

(1)16个8位整型(char);

(2)8个16位整数(short);

(3)4个32位整型(int);

(4)4个32位浮点型(float);

(5)2个64位双精度浮点型(double);

(6)2个64位整型;

整型:
typedef union __declspec(intrin_type) __declspec(align(16)) __m128i {
    __int8              m128i_i8[16];      //有符号8位整型 -128--127,128寄存器可以同时处理128/8=16个数据
    __int16             m128i_i16[8];    //有符号16位整型 -32768--32767,128寄存器可以同时处理128/16=8个数据
    __int32             m128i_i32[4];    //有符号32位整型 -2147483648--2147483647,128寄存器可以同时处理4个数据
    __int64             m128i_i64[2];    //有符号64位整型,128寄存器可以同时处理2个数据   
    unsigned __int8     m128i_u8[16];     //无符号8位整型 0--255,128寄存器可以同时处理128/8=16个数据
    unsigned __int16    m128i_u16[8];    //无符号16位整型,128寄存器可以同时处理8个数据
    unsigned __int32    m128i_u32[4];    //无符号32位整型,128寄存器可以同时处理4个数据
    unsigned __int64    m128i_u64[2];    //无符号64位整型,128寄存器可以同时处理2个数据
} __m128i;
typedef struct __declspec(intrin_type) __declspec(align(16)) __m128d {
    double              m128d_f64[2];     //64位双精度浮点型,128寄存器可以同时处理2个数据
} __m128d;
typedef union __declspec(intrin_type) __declspec(align(16)) __m128 {
    float               m128_f32[4];      //32位单精度浮点型,128寄存器可以同时处理4个数据
} __m128;


c++库:

#include <mmintrin.h>  //MMX
#include <xmmintrin.h> //SSE(include mmintrin.h)
#include <emmintrin.h> //SSE2(include xmmintrin.h)
#include <pmmintrin.h> //SSE3(include emmintrin.h)
#include <tmmintrin.h> //SSSE3(include pmmintrin.h)
#include <smmintrin.h> //SSE4.1(include tmmintrin.h)
#include <nmmintrin.h> //SSE4.2(include smmintrin.h)
#include <wmmintrin.h> //AES(include nmmintrin.h)
#include <immintrin.h> //AVX(include wmmintrin.h)
#include <intrin.h>    //所有版本(include immintrin.h)

SSE指令集:

Intrinsic函数的命名有一定的规律,一个Intrinsic通常由3部分构成,这个三个部分的具体含义如下:
第一部分为前缀_mm,表示是SSE指令集对应的Intrinsic函数。_mm256或_mm512是AVX,AVX-512指令集的Intrinsic函数前缀。
第二部分为对应的指令的操作,如_add,_mul,_load等,有些操作可能会有修饰符,如loadu将未16位对齐的操作数加载到寄存器中。
第三部分为操作的对象名及数据类型,_ps packed操作所有的单精度浮点数;
_pd packed操作所有的双精度浮点数;
_pixx(xx为长度,可以是8,16,32,64)packed操作所有的xx位有符号整数,使用的寄存器长度为64位;
_epixx(xx为长度)packed操作所有的xx位的有符号整数,使用的寄存器长度为128位;
_epuxx packed操作所有的xx位的无符号整数;
_ss操作第一个单精度浮点数。….
将这三部分组合到以其就是一个完整的Intrinsic函数,如_mm_mul_epi32 对参数中所有的32位有符号整数进行乘法运算。


2 整数加载

不需要内存对齐:

(1)加载整数(SSE3):_mm_lddqu_si128

__m128i dst = _mm_lddqu_si128(__m128i const*mem_addr)
官网解释:
dst[127:0] := MEM[mem_addr+127:mem_addr]
从未对齐的内存中加载 128 位整数数据到 dst。当数据越过缓存行边界时,此内部函数的性能可能比_mm_loadu_si128更好。
    __m128i类型指针:__m128i*
    __m128i寄存器类型变量
    读取输入指针后128位的整数数据到寄存器变量dst,数量由源数据的类型决定
    原始数据不需要内存对齐

读取指针*mem_addr后128位的整型数据,具体类型由原数据类型决定,数据不用内存对齐,返回 __m128i 类型数据,例子:

#include<opencv2/opencv.hpp>
#include<iostream>
#include <nmmintrin.h>
using namespace std;
using namespace cv;
int main()
    Mat kernel = (cv::Mat_<uchar>(1, 16) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    uchar* img = kernel.data;
    __m128i a = _mm_lddqu_si128((__m128i*) img); //加载数据,将 uchar* 转换为 __m128i*
    cout << (int)a.m128i_u8[0] << endl; //1
    cout << (int)kernel.at<uchar>(0, 0) << endl; //1
    return 0;
}

(2)加载整数(SSE2):_mm_loadu_si128

__m128i _mm_loadu_si128 (__m128i const* mem_addr)
dst[127:0] := MEM[mem_addr+127:mem_addr]
将 128 位整数数据从内存加载到 dst。 mem_addr 不需要在任何特定边界上对齐。
    __m128i类型指针:__m128i*
    __m128i寄存器类型变量
    读取输入指针后128位的整数数据到寄存器变量dst,数量由源数据的类型决定
    原始数据不需要内存对齐

(3)加载整数(SSE2):_mm_loadu_si16

__m128i _mm_loadu_si16(__m128i const* mem_addr)
dst[15:0] := MEM[mem_addr+15:mem_addr]
dst[MAX:16] := 0
将未对齐的 16 位整数从内存加载到 dst 的第一个元素中。
    指针:__m128i *
    __m128i寄存器类型变量
    读取输入指针后1个16位的整数数据到寄存器变量dst低位,其他位置为0.
    原始数据不需要内存对齐

(4)加载整数(SSE2):_mm_loadu_si32

__m128i _mm_loadu_si32 (__m128i const* mem_addr)
dst[31:0] := MEM[mem_addr+31:mem_addr]
dst[MAX:32] := 0
将未对齐的 32 位整数从内存加载到 dst 的第一个元素中。
    指针:__m128i *
    __m128i寄存器类型变量
    读取输入指针后1个32位的整数数据到寄存器变量dst低位,其他位置为0.
    原始数据不需要内存对齐

(5)加载整数(SSE2):_mm_loadu_si64

__m128i _mm_loadu_si64 (void const* mem_addr)
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[MAX:64] := 0
将未对齐的 64 位整数从内存加载到 dst 的第一个元素中。
    指针:__m128i *
    __m128i寄存器类型变量
    读取输入指针后1个64位的整数数据到寄存器变量dst低位,其他位置为0.
    原始数据不需要内存对齐


需要内存对齐:

(6)加载整数(SSE2):_mm_load_si128

__m128i _mm_load_si128 (__m128i const* mem_addr)
dst[127:0] := MEM[mem_addr+127:mem_addr]
加载128位的整数到寄存器,必须16字节对齐
    __m128i类型指针:__m128i*
    __m128i寄存器类型变量
    读取输入指针后128位的整数数据到寄存器变量dst,数量由源数据的类型决定
    原始数据必须16字节对齐

(7)加载1个64位整数(SSE2):_mm_load_si128

__m128i _mm_load_si128 (__m128i const* mem_addr)
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[MAX:64] := 0
    __m128i类型指针:__m128i*
    __m128i寄存器类型变量
    读取输入指针后1个64位的整数数据到寄存器变量dst低位,高位置为0.
    原始数据必须16字节对齐

(8)加载整数(SSE4.1):_mm_stream_load_si128

__m128i _mm_stream_load_si128 (__m128i * mem_addr)
dst[127:0] := MEM[mem_addr+127:mem_addr]
使用non-temporal memory hint将 128 位整数数据从内存加载到 dst。 mem_addr 必须在 16 字节边界上对齐,否则可能会生成一般保护异常。
   __m128i 指针:__m128i *
    __m128i寄存器类型变量
    读取输入指针后128 位整数数据到寄存器变量dst.(non-temporal memory hint:可以减少缓存污染)
    原始数据必须在16字节边界上对齐。non-temporal memory hint:
当写入内存时,写入的缓存行必须首先加载到缓存中。
写入内存时,存储在存储缓冲区中分组。通常,一旦缓冲区已满,它将被刷新到缓存/内存。对地址的连续写入将使用相同的存储缓冲区。
带有non-temporal memory hint的流式读/写通常用于减少缓存污染(通常使用 WC 内存)。这个想法是在 CPU 上保留一小组缓存线供这些指令使用。不是将缓存线加载到主缓存中,而是将其加载到这个较小的缓存中。
该评论假设以下行为(但我找不到硬件实际执行此操作的任何参考,需要测量或可靠来源,并且它可能因硬件而异): - 一旦 CPU 看到存储缓冲区已满并且它与缓存行对齐,它将直接刷新到内存,因为非临时写入绕过了主缓存。
唯一可行的方法是,如果存储缓冲区与实际写入的缓存行在刷新后发生合并。这是一个公平的假设。
请注意,如果写入的缓存行已经在主缓存中,上述方法也会更新它们。
如果使用常规内存写入而不是非临时写入,则存储缓冲区刷新也会更新主缓存。这种情况完全有可能避免读取内存中的原始缓存行。
如果使用非临时写入写入部分缓存行,则可能需要从主内存(或主缓存,如果存在)中获取缓存行,如果我们没有提前读取缓存行,可能会非常慢使用常规读取或非临时读取(这会将其放入我们单独的缓存中)。
通常,非临时高速缓存大小约为 4-8 高速缓存行。
总而言之,最后一条指令开始写入,因为它也恰好填满了存储缓冲区。存储缓冲区刷新可以避免读取写入的高速缓存行,因为硬件知道存储缓冲区是连续的并且与高速缓存行对齐。非临时写入提示仅用于避免使用我们写入的缓存行 IF 填充主缓存,并且仅当它尚未在主缓存中时。

3 float浮点数加载

不需要内存对齐:

(1)加载4个float浮点数(SSE):_mm_loadu_ps

__m128 _mm_loadu_ps (float const* mem_addr)
官网解释:
dst[127:0] := MEM[mem_addr+127:mem_addr]
   float 指针:float *
    __m128寄存器类型变量
    读取输入指针后4个64位浮点数数据到寄存器变量dst.
    原始数据不需要内存边界对齐。

(2)加载1个浮点数(SSE):_mm_load_ss

__m128 _mm_load_ps1 (float const* mem_addr)
dst[31:0] := MEM[mem_addr+31:mem_addr]
dst[63:32] := MEM[mem_addr+31:mem_addr]
dst[95:64] := MEM[mem_addr+31:mem_addr]
dst[127:96] := MEM[mem_addr+31:mem_addr]
将1个单精度(32 位)浮点元素从内存加载到 dst 的低位,并将其他 3 个元素置为0。不需要对齐。
   float 指针:float *
    __m128寄存器类型变量
    将1个单精度(32 位)浮点元素从内存加载到 dst 的低位,并将其他 3 个元素置为0。
    原始数据不需要对齐。

(3)加载1个浮点数(SSE):_mm_load1_ps

__m128 _mm_load_ps1 (float const* mem_addr)
dst[31:0] := MEM[mem_addr+31:mem_addr]
dst[63:32] := MEM[mem_addr+31:mem_addr]
dst[95:64] := MEM[mem_addr+31:mem_addr]
dst[127:96] := MEM[mem_addr+31:mem_addr]
将1个单精度(32 位)浮点元素从内存加载到 dst 的所有元素中。
   float 指针:float *
    __m128寄存器类型变量
    将1个单精度(32 位)浮点元素从内存加载到 dst 的所有元素中。
   

(4)加载2个浮点数(SSE):_mm_loadl_pi

__m128 _mm_loadl_pi (__m128 a, __m64 const* mem_addr)
dst[31:0] := MEM[mem_addr+31:mem_addr]
dst[63:32] := MEM[mem_addr+63:mem_addr+32]
dst[95:64] := a[95:64]
dst[127:96] := a[127:96]
   float 数据,__m64指针:float,__m64*
    __m128寄存器类型变量
    将 2 个单精度(32 位)浮点元素从mem_addr内存加载到 dst 的 2 个低位元素中,并将 2 个高位元素从 a 复制到 dst。
   mem_addr不需要在任何特定边界上对齐。

(5)加载2个浮点数(SSE):_mm_loadh_pi

__m128 _mm_loadh_pi (__m128 a, __m64 const* mem_addr)
dst[31:0] := a[31:0]
dst[63:32] := a[63:32]
dst[95:64] := MEM[mem_addr+31:mem_addr]
dst[127:96] := MEM[mem_addr+63:mem_addr+32]
   float 数据,float 指针:float,float *
    __m128寄存器类型变量
    将 2 个单精度(32 位)浮点元素从mem_addr内存加载到 dst 的 2 个高位元素中,并将 2 个低位元素从 a 复制到 dst。
   mem_addr不需要在任何特定边界上对齐。


需要内存对齐:

(6)加载4个float浮点数(SSE):_mm_load_ps

__m128 _mm_load_ps (float const* mem_addr)
官网解释:
dst[127:0] := MEM[mem_addr+127:mem_addr]
将 128 位(由 4 个打包的单精度(32 位)浮点元素组成)从内存加载到 dst 中。mem_addr必须在 16 字节边界上对齐,否则可能会生成常规保护异常。
   float 指针:float *
    __m128寄存器类型变量
    读取输入指针后4个64位浮点数数据到寄存器变量dst.
    原始数据必须在16字节边界上对齐。

(7)加载1个浮点数(SSE):_mm_load_ps1

__m128 _mm_load_ps1 (float const* mem_addr)
dst[31:0] := MEM[mem_addr+31:mem_addr]
dst[63:32] := MEM[mem_addr+31:mem_addr]
dst[95:64] := MEM[mem_addr+31:mem_addr]
dst[127:96] := MEM[mem_addr+31:mem_addr]
将1个单精度(32 位)浮点元素从内存加载到 dst 的所有元素中。
   float 指针:float *
    __m128寄存器类型变量
    读取输入指针后1个64位浮点数数据到寄存器变量dst所有元素.
    原始数据必须在16字节边界上对齐。

(8)加载4个浮点数(SSE):_mm_loadr_ps

__m128 _mm_loadr_ps (float const* mem_addr)
dst[31:0] := MEM[mem_addr+127:mem_addr+96]
dst[63:32] := MEM[mem_addr+95:mem_addr+64]
dst[95:64] := MEM[mem_addr+63:mem_addr+32]
dst[127:96] := MEM[mem_addr+31:mem_addr]
   float 指针:float *
    __m128寄存器类型变量
    将 4 个单精度(32 位)浮点元素从mem_addr内存按照相反的顺序加载到 dst。
   mem_addr必须16字节对齐。
 

4 double浮点数加载

不需要内存对齐:

(1)加载2个double浮点数(SSE2):_mm_loadu_pd

__m128d _mm_loadu_pd (double const* mem_addr)
dst[127:0] := MEM[mem_addr+127:mem_addr]
    double const* mem_addr
    __m128d 寄存器类型变量
    读取输入指针后2个double数据
    原始数据不需要16字节对齐

(2)加载1个double浮点数(SSE3):_mm_loaddup_pd

__m128d _mm_loaddup_pd (double const* mem_addr)
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[127:64] := MEM[mem_addr+63:mem_addr]
    double 类型指针:double *
    __m128d 寄存器类型变量
    读取输入指针后1个double数据到寄存器的两个元素
    原始数据不需要16字节对齐

(3)加载2个double浮点数(SSE2):_mm_loadh_pd

__m128d _mm_loadh_pd (__m128d a, double const* mem_addr)
dst[63:0] := a[63:0]
dst[127:64] := MEM[mem_addr+63:mem_addr]
    __m128d a, double const* mem_addr
    __m128d
    读取输入指针mem_addr后1个double数据到寄存器高64位,低64位从a的低64位获取
    原始数据不需要16字节对齐

(4)加载2个double浮点数(SSE2):_mm_loadl_pd

__m128d _mm_loadl_pd (__m128d a, double const* mem_addr)
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[127:64] := a[127:64]
    __m128d a, double const* mem_addr
    __m128d
    读取输入指针mem_addr后1个double数据到寄存器低64位,高64位从a的高64位获取
    原始数据不需要16字节对齐

(5)加载1个double浮点数(SSE2):_mm_load1_pd

__m128d _mm_load1_pd (double const* mem_addr)
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[127:64] := MEM[mem_addr+63:mem_addr]
    double const* mem_addr
    __m128d
    读取输入指针mem_addr后1个double数据到寄存器的两个元素中
    原始数据不需要16字节对齐

(6)加载1个double浮点数(SSE2):_mm_load_sd

__m128d _mm_load_sd (double const* mem_addr)
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[127:64] := 0
将1个64位浮点数从内存加载到 dst 的低64位中,将高位置为0.可以不用字节对齐。
    double 类型指针:double *
    __m128d 寄存器类型变量
    读取输入指针后1个double数据,将高位置为0
    原始数据不需要16字节对齐


需要内存对齐:

(7)加载2个double双精度浮点数(SSE2):_mm_load_pd

__m128d  _mm_load_pd (double const* mem_addr)
官网解释:
dst[127:0] := MEM[mem_addr+127:mem_addr]
    double 类型指针:double *
    __m128d 寄存器类型变量
    读取输入指针后128位的double数据类型(2个)
    原始数据必须16字节对齐

(8)加载2个double双精度浮点数(SSE2):_mm_loadr_pd

__m128d _mm_loadr_pd (double const* mem_addr)
官网解释:
dst[63:0] := MEM[mem_addr+127:mem_addr+64]
dst[127:64] := MEM[mem_addr+63:mem_addr]
    double const* mem_addr
    __m128d 寄存器类型变量
    读取输入指针后2个double数据类型,按相反的顺序保存。
    原始数据必须16字节对齐

(9)加载1个double双精度浮点数(SSE2):_mm_load_pd1

__m128d _mm_load_pd1 (double const* mem_addr)
官网解释:
dst[63:0] := MEM[mem_addr+63:mem_addr]
dst[127:64] := MEM[mem_addr+63:mem_addr]
将双精度(64 位)浮点元素从内存加载到 dst 的两个元素中。
    double 类型指针:double *
    __m128d 寄存器类型变量
    读取输入指针后1个double数据,将高低位都置为这个double数据
    原始数据必须16字节对齐

读取一个double数据,寄存器中两个元素都为该数据:

#include<opencv2/opencv.hpp>
#include<iostream>
#include <intrin.h>
using namespace std;
using namespace cv;
int main()
	Mat kernel = (cv::Mat_<double>(1, 16) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
	double* img = (double*)kernel.data;
	__m128d a = _mm_load_pd1((double*) img);