SSE指令集学习之旅(一)

文章目录

  • SSE指令集学习之旅(一)
    • 1、SSE介绍
    • 2、如何使用SSE指令
    • 3、SSE相关数据类型
    • 4、 Intrinsic 函数的命名
    • 5、常用的SSE指令
    • 6、SSE指令应用实例

1、SSE介绍

SSE (为 Stream SIMD Extentions 的缩写)数据流单指令多数据扩展,是由 Intel 公司,在1999年推出 Pentinum III 处理器时,同时推出的新指令集,是继 MMX (为 Multi Media eXtension 的缩写)多媒体扩展指令集之后推出的新一代CPU指令集。如同其名称所表示的,SSE 是一种 SIMD 指令集。所谓的 SIMD (为 Single Instruction Multiple Data 的缩写)单指令多数据技术,其作用是通过一个指令同时对多个数据进行相同的计算操作,实现数据并行处理,提高程序的运算速度。較早的 MMXAMD 的 3DNow! 也都是 SIMD 指令集。

SSE 本质上类似于一个向量处理器,包括了4个主要部分:单精确度浮点数运算指令、整数运算指令(为 MMX 的延伸,并与 MMX 使用同样的寄存器)、Cache控制指令、状态控制指令。

如下图所示,SSE 新增了八个新的128位元寄存器(XMM0 ~ XMM7),这些128位元寄存器,可以用来存放四个32位元的单精度浮点数。也就是说,SSE 中所有计算都是一次性针对四个浮点数来完成的,这种批处理会带来显著的效率提升。使用 SSE 优化之后,我们的代码不一定会得到4倍速的提升,因为编译器可能已经自动对某些代码进行 SSE 优化了。


        SSE 指令一次性能处理16个字节型数据,8个short类型数据,4个int类型数据或4个单精度浮点型数据,2个双精度浮点型数据。如下图所示:


        举例说明:计算一个很长的单精度浮点型数组中每个元素的平方根。在纯C++下面实现可以这样写:

for each f in array
        {
            f = sqrt(f)
        }

其底层实现如下:

for each f in array
        {
            把f从内存加载到浮点寄存器中
            计算平方根
            再把计算结果从寄存器中取出放入内存
        }

采用SSE指令集后实现如下:

for each 4 members in array
        {
            把数组中的这4个数加载到一个128位的SSE寄存器中
            在一个CPU指令执行周期中完成计算这4个数的平方根的操作
            把所得的4个结果取出写入内存
        {

2、如何使用SSE指令

要在 CC++ 程序中使用 SSE 指令,一般有两种方式:一种是直接在 C/C++ 中嵌入(汇编)指令;二是使用 Intel C++ Compiler 或是 Microsoft Visual C++ 中提供的支持 SSE 指令集的 intrinsics 内联函数。从代码可读和维护角度讲,推荐使用 intrinsics 内联函数的形式。intrinsics 是对 MMXSSE 等指令集的一种封装,以函数的形式提供,使得程序员更容易编写和使用这些高级指令,在编译的时候,这些函数会被内联为汇编,不会产生函数调用的开销。

举例如下:使用纯 C++

#include <iostream>
#include <chrono>void  computeArrayCPlusPlus(float* pArray1, float* pArray2, float* pResult, int nSize)
{float* pSource1 = pArray1;float* pSource2 = pArray2;float* pDest = pResult;for (int i = 0; i < nSize; i++){*pDest = (float)sqrt((*pSource1) * (*pSource1) + (*pSource2)* (*pSource2)) + 0.5f;pSource1++;pSource2++;pDest++;}
}int main()
{float pArray1[50000] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 };float pArray2[50000] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 };float pResult[50000];auto start = std::chrono::steady_clock::now();computeArrayCPlusPlus(pArray1, pArray2, pResult, 50000);auto end = std::chrono::steady_clock::now();std::chrono::duration<double, std::micro> elapsed = end - start;for (int i = 0; i < 10; i++){if (i != 9){std::cout << pResult[i] << ", ";}else{std::cout << pResult[i] << std::endl;}}std::cout << "纯C++运行时间: " << elapsed.count() << "微秒" << std::endl;return 0;
}

使用 SSE 指令集的 intrinsics 内联函数

#include <iostream>
#include <pmmintrin.h>         //SSE3指令集
#include <chrono>void ComputeArrayCPlusPlusSSE(float* pArray1, float* pArray2, float* pResult, int nSize)
{int nLoop = nSize / 4;//__m128属于单精度浮点型__m128 m1, m2, m3, m4;__m128* pSrc1 = (__m128*) pArray1;__m128* pSrc2 = (__m128*) pArray2;__m128* pDest = (__m128*) pResult;//m0_5[0, 1, 2, 3] = [0.5, 0.5, 0.5, 0.5]__m128 m0_5 = _mm_set_ps1(0.5f);for (int i = 0; i < nLoop; i++){//m1 = *pSrc1 * *pSrc1m1 = _mm_mul_ps(*pSrc1, *pSrc1);//m2 = *pSrc2 * *pSrc2m2 = _mm_mul_ps(*pSrc2, *pSrc2);//m3 = m1 + m2m3 = _mm_add_ps(m1, m2);//m4 = sqrt(m3)m4 = _mm_sqrt_ps(m3);//*pDest = m4 + 0.5*pDest = _mm_add_ps(m4, m0_5);pSrc1++;pSrc2++;pDest++;}
}int main()
{float pArray1[50000] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 };float pArray2[50000] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 };float pResult[50000];auto start = std::chrono::steady_clock::now();ComputeArrayCPlusPlusSSE(pArray1, pArray2, pResult, 50000);auto end = std::chrono::steady_clock::now();std::chrono::duration<double, std::micro> elapsed = end - start;for (int i = 0; i < 10; i++){if (i != 9){std::cout << pResult[i] << ", ";}else{std::cout << pResult[i] << std::endl;}}std::cout << "intrinsics 内联函数的运行时间: " << elapsed.count() << "微秒" << std::endl;return 0;
}

使用 SSE 嵌入汇编

#include <iostream>
#include <chrono>void ComputeArrayAssemblySSE(float* pArray1, float* pArray2, float* pResult, int nSize)
{int nLoop = nSize / 4;float f = 0.5f;_asm{//xmm2[0] = 0.5movss xmm2,f//xmm2[1, 2, 3] = [xmm2[0], xmm2[0], xmm2[0]]shufps xmm2,xmm2,0//输入的源数组1的地址送往esimov     esi,pArray1//输入的源数组2的地址送往edxmov     edx,pArray2//输入的结果数组的地址送往edimov     edi,pResult//循环次数送往ecxmov     ecx,nLoopstart_loop :                               //开始循环movaps     xmm0,[esi]              //xmm0 = [esi]mulps      xmm0,xmm0               //xmm0 = xmm0 * xmm0movaps     xmm1,[edx]              //xmm1 = [edx]mulps      xmm1,xmm1               //xmm1 = xmm1 * xmm1addps      xmm0,xmm1               //xmm0 = xmm0 + xmm1sqrtps     xmm0,xmm0               //xmm0 = sqrt(xmm0)addps      xmm0,xmm2               //xmm0 = xmm0 + xmm2movaps     [edi],xmm0              //[edi] = xmm0add esi,16                         //esi += 16 移动16个字节add edx,16                         //edx += 16 移动16个字节add edi,16                         //edi += 16 移动16个字节dec ecx                            //ecx-- ecx -= 1jnz start_loop                     //如果ecx不为零转向start_Loop}
}int main()
{float pArray1[50000] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 };float pArray2[50000] = { 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 };float pResult[50000];auto start = std::chrono::steady_clock::now();ComputeArrayAssemblySSE(pArray1, pArray2, pResult, 50000);auto end = std::chrono::steady_clock::now();std::chrono::duration<double, std::micro> elapsed = end - start;for (int i = 0; i < 10; i++){if (i != 9){std::cout << pResult[i] << ", ";}else{std::cout << pResult[i] << std::endl;}}std::cout << "嵌入汇编的运行时间: " << elapsed.count() << "微秒" << std::endl;return 0;
}

由于Vs x64平台下不支持内联汇编,所以在此就无法运行了。如果对此感兴趣的话,可以参考以下博客进行配置。https://www.cnblogs.com/betterwgo/p/8145746.html

在此,我只针对使用 SSE 指令集的 intrinsics 内联函数进行学习。

在C++中,想要使用SSE指令,则需要包含以下对应的头文件:


        例如,要使用 SSE3,则需要导入:#include <tmmintrin.h>

如果不关心使用那个版本的 SSE 指令,则可以包含所有版本:#include <intrin.h>

3、SSE相关数据类型

Intrinsic 使用的数据类型与其寄存器是相对应,如下:

·64位 MMX 指令集使用

·128位 SSE 指令集使用

·256位 AVX 指令集使用

SSE 指令中intrinsics函数的数据类型为:__m128(单精度浮点数),如果使用sizeof(__m128)计算该类型大小,结果为16,即等于四个浮点数长度。__declspec(align(16))做为数组定义的修释符,表示该数组是以16字节为边界对齐的,因为SSE指令大部分支持这种格式的内存数据。其__m128数组定义如下:

typedef struct __declspec(intrin_type) __declspec(align(16)) __m128
{float m128_f32[4];
} __m128;

SSE 指令处理的每一个浮点数数组必须把其中需要处理的数每16个字节(128位二进制)分为一组。一个静态数组(static array)可由__declspec(align(16))关键字声明:

__declspec(align(16)) float m_fArray[ARRAY_SIZE];

动态数组(dynamic array)可由_aligned_malloc函数为其分配空间:

float* m_fArray;
m_fArray = (float*) _aligned_malloc(ARRAY_SIZE * sizeof(float), 16);

由_aligned_malloc函数分配空间的动态数组可以由_aligned_free函数释放其占用的空间:

_aligned_free(m_fArray);

除__m128外、还包括__m128d(双精度浮点数)和__m128i(整型)。其中__m128i是一个共用体类型,其定义如下:

typedef union __declspec(intrin_type)_CRT_ALIGN(16)__m128i
{__int8                m128i_i8[16];  //char__int16               m128i_i16[8];  //short__int32               m128i_i32[4];  //int__int64               m128i_i64[2];  //long longunsigned __int8       m128i_u8[16];  //ucharunsigned __int16      m128i_u16[8];  //ushortunsigned __int32      m128i_u32[4];  //uintunsigned __int64      m128i_u64[2];  //ulonglong
}__m128i;

对于__m128i联合体,不同成员包含不同的数据类型。每个成员都是一个数组,数组中填充这相应的数据,并且根据数据长度的不同数组的长度也不同(数组长度 = 128 / 每个数据的长度(位))。在使用的时候要特别注意操作数据的类型,也就是数组长度。例如:定义了一个_m128i yy;当变量yy去存储32位的有符号整型时使用的数据为:0, 0, 1024, 1024;但当yy用于存储64位的有符号整型时使用的数据是:0, 4398046512128。

4、 Intrinsic 函数的命名

Intrinsic函数的命名也是有一定的规律,一个Intrinsic通常由3部分构成,这个三个部分的具体含义如下:

第一部分为前缀_mm,表示是SSE指令集对应的Intrinsic函数。_mm256是AVX指令集的Intrinsic函数前缀。

第二部分为对应的指令的操作,如_add,_mul,_load等,有些操作可能会有修饰符,如loadu将未16位对齐的操作数加载到寄存器中。

第三部分通常由两个字母组成。第一个字母表示对结果变量的影响方式,为p或s。 p(packed:包裹指令) :该指令对xmm寄存器中的每个元素进行运算,即一次对四个浮点数(data0~data3)均进行计算;s(scalar:标量指令):该指令对寄存器中的第一个元素进行运算,即一次只对xmm寄存器中的data0进行计算。


        第二个字母表示参与运算的数据类型,s表示32位浮点数,d表示64位浮点数,i32表示带符号32位整型,i64表示带符号64位整型,u32表示无符号32位整型,以此类推。由于SSE只支持32位浮点数的运算,所以你可能会在这些指令封装函数中找不到包含非s修饰符的,但你可以在MMX和SSE2的指令集中去认识它们。

_pixx(xx为长度,可以是8,16,32,64)packed操作所有的xx位有符号整数,使用的寄存器长度为64位;_epixx(xx为长度)packed操作所有的xx位的有符号整数,使用的寄存器长度为128位;_epuxx packed操作所有的xx位的无符号整数。

将以上三部分组合到一起就是一个完整的Intrnsic函数。

5、常用的SSE指令

load系列,用于加载数据,从内存到寄存器,大部分需要16字节对齐

//_mm_load_ss用于scalar的加载。加载一个单精度浮点数到寄存器的第一字节,其它三个字节清0,(r0 := *p, r1 := r2 := r3 := 0.0)。
__m128 _mm_load_ss (float *p)//_mm_load_ps用于packed的加载(下面的都是用于packed的),要求p的地址是16字节对齐,否则读取的结果会出错,(r0 := p[0], r1 := p[1], r2 := p[2], r3 := p[3])。
__m128 _mm_load_ps (float *p)//_mm_load1_ps表示将p地址的值加载到寄存器的四个字节,需要多条指令完成,所以,从性能考虑,在内层循环不要使用这类指令。(r0 := r1 := r2 := r3 := *p)。
__m128 _mm_load1_ps (float *p)//_mm_loadh_pi和_mm_loadl_pi分别用于从两个参数高底字节等组合加载。具体参考手册。
__m128 _mm_loadh_pi (__m128 a, __m64 *p)
__m128 _mm_loadl_pi (__m128 a, __m64 *p)//_mm_loadr_ps表示以_mm_load_ps反向的顺序加载,需要多条指令完成,当然,也要求地址是16字节对齐。(r0 := p[3], r1 := p[2], r2 := p[1], r3 := p[0])。
__m128 _mm_loadr_ps (float *p)//_mm_loadu_ps和_mm_load_ps一样的加载,但是不要求地址是16字节对齐,对应指令为movups。
__m128 _mm_loadu_ps (float *p)

set系列,用于加载数据,类似于load操作,但是大部分需要多条指令完成,可能不需要16字节对齐

//_mm_set_ss对应于_mm_load_ss的功能,不需要字节对齐,需要多条指令。(r0 = w, r1 = r2 = r3 = 0.0)
__m128 _mm_set_ss (float w)//_mm_set_ps对应于_mm_load_ps的功能,参数是四个单独的单精度浮点数,所以也不需要字节对齐,需要多条指令。(r0=w, r1 = x, r2 = y, r3 = z,注意顺序)
__m128 _mm_set_ps (float z, float y, float x, float w)//_mm_set1_ps对应于_mm_load1_ps的功能,不需要字节对齐,需要多条指令。(r0 = r1 = r2 = r3 = w)
__m128 _mm_set1_ps (float w)//_mm_setr_ps对应于_mm_loadr_ps功能,不需要字节对齐,需要多条指令。(r0=z, r1 = y, r2 = x, r3 = w,注意顺序)
__m128 _mm_setr_ps (float z, float y, float x, float w)//_mm_setzero_ps是清0操作,只需要一条指令。(r0 = r1 = r2 = r3 = 0.0)
__m128 _mm_setzero_ps ()

store系列,将计算结果等SSE寄存器的数据保存到内存中,与load系列函数的功能对应,基本上都是一个反向的过程。

//_mm_store_ss:一条指令,*p = a0
void _mm_store_ss (float *p, __m128 a)//_mm_store_ps:一条指令,p[i] = a[i]。
void _mm_store_ps (float *p, __m128 a)//_mm_store1_ps:多条指令,p[i] = a0。
void _mm_store1_ps (float *p, __m128 a)//_mm_storeh_pi,_mm_storel_pi:值保存其高位或低位。
void _mm_storeh_pi (__m64 *p, __m128 a)
void _mm_storel_pi (__m64 *p, __m128 a)//_mm_storer_ps:反向,多条指令。
void _mm_storer_ps (float *p, __m128 a)//_mm_storeu_ps:一条指令,p[i] = a[i],不要求16字节对齐。
void _mm_storeu_ps (float *p, __m128 a)//_mm_stream_ps:直接写入内存,不改变cache的数据。
void _mm_stream_ps (float *p, __m128 a)

算数指令系列,SSE 提供了大量的浮点运算指令,包括加法、减法、乘法、除法、开方、最大值、最小值等等。

//返回一个__m128的寄存器,r0=_A0+_B0, r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_add_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0+_B0, r1=_A1+_B1, r2=_A2+_B2, r3=_A3+_B3,
extern __m128 _mm_add_ps (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0-_B0, r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_sub_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0-_B0, r1=_A1-_B1, r2=_A2-_B2, r3=_A3-_B3,
extern __m128 _mm_sub_ps (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0*_B0, r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_mul_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0*_B0, r1=_A1*_B1, r2=_A2*_B2, r3=_A3*_B3,
extern __m128 _mm_mul_ps (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0/_B0, r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_div_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_A0/_B0, r1=_A1/_B1, r2=_A2/_B2, r3=_A3/_B3,
extern __m128 _mm_div_ps (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_sqrt(A0), r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_sqrt_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_sqrt(A0), r1=_sqrt(A1), r2=sqrt(A2), r3=sqrt(A3),
extern __m128 _mm_sqrt_ps (__m128 _A)//返回一个__m128的寄存器,r0=_max(_A0,_B0), r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_max_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_max(_A0,_B0), r1=_max(_A1,_B1), r2=_max(_A2,_B2), r3=_max(_A3,_B3),
extern __m128 _mm_max_ps (__m128 _A)//返回一个__m128的寄存器,r0=_min(_A0,_B0), r1 = A1, r2 = A2, r3 = A3
extern __m128 _mm_min_ss (__m128 _A, __m128 _B)//返回一个__m128的寄存器,r0=_min(_A0,_B0), r1=_min(_A1,_B1), r2=_min(_A2,_B2), r3=_min(_A3,_B3),
extern __m128 _mm_min_ps (__m128 _A)//将128位值都赋值为0
extern _mm_setzero_si128()//返回一个_m128i的寄存器,将S0和S1的低64位的数以8位为单位进行交错
//S0:A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
//S1:B15 B14 B13 B12 B11 B10 B9 B8 B7 B6 B5 B4 B3 B2 B1 B0
//_mm_unpacklo_epi8(S0,S1):B7 A7 B6 A6 B5 A5 B4 A4 B3 A3 B2 A2 B1 A1 B0 A0
extern _mm_unpacklo_epi8(_m128i S0,_m128i S1)//返回一个_m128i的寄存器,将S0和S1的高64位的数以8位为单位进行交错
extern _mm_unpackhi_epi8(_m128i S0,_m128i S1)//返回一个_m128i的寄存器,将S0和S1中对应位置的16bit整数分别相减
extern _mm_sub_epi16(_m128i S0,_m128i S1)//返回一个_m128i的寄存器,它含有8个16位的整数,分别为S0和S1对应位置的16位的整数相乘结果的低16位数据
extern _mm_mullo_epi16(_m128i S0,_m128i S1)//返回一个_m128i的寄存器,将寄存器S0中的8个16位整数按照计数进行算术右移
extern _mm_srai_epi16(_m128i S0,int count)//返回一个_m128i的寄存器,将S0和S1中共16个16位数,放入存8位数的数组里,并进行饱和处理
extern _mm_packs_epi16(_m128i S0,_m128i S1)//返回一个_m128i的寄存器,将S0和S1中对应位置的8bit整数分别相加;
extern _mm_add_epi8(_m128i S0,_m128i S1)//将存储在缓存器S0中的数据存在指针p指向的地址
extern _mm_stream_si128(_m128i *p,_m128i S0)//更多可参考博客:https://blog.csdn.net/weixin_44470443/article/details/99819791

数据类型转换系列。在做图像处理时,由于像素通道值是8位的无符号整数,而与其运算的往往又是浮点数,这就需要将8位无符号整数转换为浮点数;运算完毕后,得到的结果要写回图像通道,就需要将浮点数转换回来。但有时计算要考虑超出8位的截断,即大于255的数据。
    类型转换主要一下几种:

1、浮点数和整数的转换及32位浮点数和64位浮点数之间的转换。这种转换简单直接,只需要调用相应的函数指令即可。

2、有符号整数的高位扩展。将8位、16位、32位有符号整数扩展为16位、32位、64位。

3、有符号整数的截断。将16位、32位、64位有符号压缩为8位、16位、32位。

4、无符号整数到有符号整数的扩展

//(xx是位数8/16/32/64)这是有符号整数之间的转换。
_mm_cvtepixx_epixx //整数到单精度浮点数之间的转换。
mm_cvtepixx_ps//整数到双精度浮点数之间的转换。
_mm_cvtepixx_pd//单精度浮点数转换为有符号32位整数(带截断操作)
__mm_cvttps_si32  //无符号整数向有符号整数的扩展,采用高位0扩展的方式,这些函数是对无符号高位0扩展变成相应位数的有符号整数。没有32位无符号整数转换为16位有符号整数这样的操作。
_mm_cvtepuxx_epixx//无符号整数转换为单精度浮点数。
_mm_cvtepuxx_ps// 无符号整数转换为双精度浮点数。
_mm_cvtepuxx_pd

上面的数据转换还少了一种,整数的饱和转换。即超过最大值的以最大值来计算,例如8位无符号整数最大值为255,则转换为8位无符号时超过255的值视为255。
    整数的饱和转换有两种:

有符号之间的 SSE 的Intrinsic函数提供了两种

//用于将16/32位的有符号整数饱和转换为8/16位有符号整数。
__m128i _mm_packs_epi32(__m128i a, __m128i b)
__m128i _mm_packs_epi16(__m128i a , __m128i b)

有符号到无符号之间的

//用于将16/32位的有符号整数饱和转换为8/16位无符号整数
__m128i _mm_packus_epi32(__m128i a, __m128i b)
__m128i _mm_packus_epi16(__m128i a , __m128i b)

6、SSE指令应用实例

一般而言,使用 SSE 指令写程序,步骤如下:

1、使用load/set函数将数据从内存加载到SSE寄存器中

2、使用相关SSE指令完成计算

3、使用store系列函数将结果从寄存器保存到内存,供后面使用

例一:使用SSE指令完成加法运算(不要求字节对齐)

#include <tmmintrin.h>
#include <iostream>using namespace std;int main(int argc, char* argv[])
{float op1[4] = { 1.0, 2.0, 3.0, 4.0 };float op2[4] = { 1.0, 2.0, 3.0, 4.0 };float result[4];__m128  a;__m128  b;__m128  c;//Loada = _mm_loadu_ps(op1);b = _mm_loadu_ps(op2);//Calculatec = _mm_add_ps(a, b);  // c = a + b//Store_mm_storeu_ps(result, c);cout << result[0] << endl;cout << result[1] << endl;cout << result[2] << endl;cout << result[3] << endl;system("pause");return 0;
}

例二:使用SSE指令完成加法运算(要求字节对齐)

#include <tmmintrin.h>
#include <iostream>using namespace std;int main(int argc, char* argv[])
{__declspec(align(16)) float op1[4] = { 1.0, 2.0, 3.0, 4.0 };__declspec(align(16)) float op2[4] = { 1.0, 2.0, 3.0, 4.0 };//_MM_ALIGN16等同于__declspec(align(16))_MM_ALIGN16 float result[4];        __m128  a;__m128  b;__m128  c;//Loada = _mm_load_ps(op1);b = _mm_load_ps(op2);//Calculatec = _mm_add_ps(a, b);   // c = a + b//Store_mm_store_ps(result, c);cout << result[0] << endl;cout << result[1] << endl;cout << result[2] << endl;cout << result[3] << endl;system("pause");return 0;
}

例三:使用SSE指令完成多个数据的加法运算(要求字节对齐)

如果想使用SSE计算一个浮点型数组中每个元素的平方根,我们不必去声明__m128类型的数组,可以直接将你的数组强制类型转换成__m128*,然后使用SSE的命令操作这个数组。

 __declspec(align(16)) float array[]  = { 1.0,  2.0, 3.0, 4.0 };__m128* ptr = (__m128*)array;__m128 t = _mm_sqrt_ps(*ptr);
#include <tmmintrin.h>
#include <iostream>
#include<Windows.h>using namespace std;void sse_add(float *srcA, float *srcB, float *dest, int n)
{int len = n >> 2;for (int i = 0; i < len; i++) {*(__m128*)(dest + i * 4) = _mm_add_ps(*(__m128*)(srcA + i * 4), *(__m128*)(srcB + i * 4));}
}void normal_add(float *srcA, float *srcB, float *dest, int n)
{for (int i = 0; i < n; i++) {dest[i] = srcA[i] + srcB[i];}
}int main() {DWORD timeStart = 0, timeEnd = 0;//申请的内存中存放的数据个数const int size = 10000; //循环计算的次数,便于观察执行效率const int count = 10000;//分配16字节对齐的内存_MM_ALIGN16 float *srcA = (_MM_ALIGN16 float*)_mm_malloc(sizeof(float)*size, 16);_MM_ALIGN16 float *srcB = (_MM_ALIGN16 float*)_mm_malloc(sizeof(float)*size, 16);_MM_ALIGN16 float *dest = (_MM_ALIGN16 float*)_mm_malloc(sizeof(float)*size, 16);//初始化for (int i = 0; i < size; i++) {srcA[i] = (float)i;}memcpy_s(srcB, sizeof(float) * size, srcA, sizeof(float) * size);//标准加法timeStart = GetTickCount();for (int i = 0; i < count; i++) {normal_add(srcA, srcB, dest, size);}timeEnd = GetTickCount();cout << "标准加法" << (timeEnd - timeStart) << "毫秒" << endl;// SSE指令加法timeStart = GetTickCount();for (int i = 0; i < count; i++) {sse_add(srcA, srcB, dest, size);}timeEnd = GetTickCount();cout << "SSE加法" << (timeEnd - timeStart) << "毫秒" << endl;// 释放内存_mm_free(srcA);_mm_free(srcB);_mm_free(dest);system("pause");return 0;
}

上述内容参考以下博客:(本人也是初学者,如果有问题,请及时在评论区指出,感谢

SSE指令集学习之旅(一)相关推荐

  1. SSE指令集学习之旅(二)

    SSE指令集学习之旅(二) 文章目录 SSE指令集学习之旅(二) 1.BGR->GRAY 2.summarize(归纳总结) 1.BGR->GRAY 知识来源:SSE图像算法优化系列一 代 ...

  2. SSE指令集学习:Compiler Intrinsic

    大多数的函数是在库中,Intrinsic Function却内嵌在编译器中(built in to the compiler). 1. Intrinsic Function Intrinsic Fun ...

  3. SSE/AVX指令集学习笔记

    ​ 因为最近在做SSE/AVX指令集优化视频编码的某些模块,所以要学习SSE指令集的用法.本帖主要记录本人用到的函数的用法. 一.SSE指令(128位寄存器) __m128i _mm_load_si1 ...

  4. HALCON学习之旅(七)

    HALCON学习之旅(七) 文章目录 HALCON学习之旅(七) 1.MFC与Halcon混合编程 2.C#与Halcon混合编程 3.Halcon测量助手使用 4.Halcon实例进阶一(拟合区域椭 ...

  5. 单周期CPU实验之学习之旅

    初接触到CPU(处理器)的实现,有很多东西需要先学习一下,才能了解其中的原理,更好地实现它.首先,你需要深入了解MIPS指令集,理解其各个指令的执行过程:其次,你需要掌握Verilog语言的使用,理解 ...

  6. 泰凌微ble mesh蓝牙模组天猫精灵学习之旅 ② 如何实现 微信小程序蓝牙控制 Ble Mesh模组 安信可TB02,全部开源!

    本<泰凌微ble mesh蓝牙模组天猫精灵学习之旅>系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1.小 ...

  7. 泰凌微ble mesh蓝牙模组天猫精灵学习之旅④如何在Android开发低功耗蓝牙ble控制 TB-02 模块,代码工程全部开源!(附带Demo)

    本<泰凌微ble mesh蓝牙模组天猫精灵学习之旅>系列博客学习由半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1.小白也痴迷,如 ...

  8. hadoop学习之旅1

    大数据介绍 大数据本质也是数据,但是又有了新的特征,包括数据来源广.数据格式多样化(结构化数据.非结构化数据.Excel文件.文本文件等).数据量大(最少也是TB级别的.甚至可能是PB级别).数据增长 ...

  9. 基于设计模式的学习之旅-----访问者模式(附源码)

    基于设计模式的学习之旅-----访问者模式 1.初始访问者模式 2.什么是访问者模式 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 3.模 ...

最新文章

  1. 开源!mathAI手写拍照自动能解高数题,还不快试试?
  2. 域密码自助重置系统----绑定私人邮箱信息自助重置(一)
  3. dns-sd._udp.domain. 域名发现 本质和MDNS同
  4. 判断N!阶乘中末尾0的个数
  5. Python面试中需要注意的几点事项!
  6. box2d 碰撞检测_(译)如何使用box2d来做碰撞检测(且仅用来做碰撞检测)
  7. 光流法测试代码_高效的企业测试-工作流和代码质量(4/6)
  8. 当知识图谱遇上推荐系统之PippleNet模型(论文笔记二)
  9. 设计模式---模板模式(C++实现)
  10. 考完试 记录一下复习资料 人工智能原理知识点整理
  11. qrect在图片上显示矩形框_Mac上用LabelImg手动标记图片
  12. Atitit 军事学 之 军事思想学与打猎学总结以及在软件行业中的应用 attilax著 1. 军事思想在软件行业技术开发中的想通之处 1 1.1. 软件开发本质上是一种作战,敌人是时间与费用成本
  13. 【旅行商问题】基于matlab免疫算法求解旅行商问题【含Matlab源码 195期】
  14. 机器人开发--OS系统介绍
  15. java万年历程序代码_JAVA万年历程序代码
  16. ssis sql oracle,[SQL][SSIS]透過 SSIS 連接 Oracle 的資料庫
  17. aspen稳态导出动态_Aspen Dynamics在控制中的应用
  18. 用户画像标签体系建设指南
  19. linux自定义oem分区,怎么样把oem分区里的数据移到虚拟机的硬盘里?
  20. 如何利用计算机隐藏文件,如何查找隐藏的计算机文件夹

热门文章

  1. linux下执行java_Linux下运行java项目
  2. python入门之字符串处理_Python基础之字符串操作,格式化。
  3. java 参数类型不确定_java泛型的那些事
  4. typora 分割线_实战 | 五分钟,使用Typora+PicGo提升百倍写作效率
  5. grafana的+按钮_基于 Prometheus、Grafana 的 EMQ X 物联网 MQTT 服务器可视化运维监控...
  6. MacOS 安装PHP5.6
  7. LCT模板(无讲解)
  8. 【Linux基础】查看硬件信息-CPU
  9. 2015年创业中遇到的技术问题:141-150
  10. Activity 半透明样式