SSE指令集学习之旅(一)

SSE指令集学习之旅(一)SSE 指令集学习之旅 一 sse 指令集

大家好,欢迎来到IT知识分享网。

SSE指令集学习之旅(一)

1、SSE介绍

        SSE (为 Stream SIMD Extentions 的缩写)数据流单指令多数据扩展,是由 Intel 公司,在1999年推出 Pentinum III 处理器时,同时推出的新指令集,是继 MMX (为 Multi Media eXtension 的缩写)多媒体扩展指令集之后推出的新一代CPU指令集。如同其名称所表示的,SSE 是一种 SIMD 指令集。所谓的 SIMD (为 Single Instruction Multiple Data 的缩写)单指令多数据技术,其作用是通过一个指令同时对多个数据进行相同的计算操作,实现数据并行处理,提高程序的运算速度。較早的 MMX 和 AMD 的 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 * *pSrc1 m1 = _mm_mul_ps(*pSrc1, *pSrc1); //m2 = *pSrc2 * *pSrc2 m2 = _mm_mul_ps(*pSrc2, *pSrc2); //m3 = m1 + m2 m3 = _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.5 movss xmm2,f //xmm2[1, 2, 3] = [xmm2[0], xmm2[0], xmm2[0]] shufps xmm2,xmm2,0 //输入的源数组1的地址送往esi mov esi,pArray1 //输入的源数组2的地址送往edx mov edx,pArray2 //输入的结果数组的地址送往edi mov edi,pResult //循环次数送往ecx mov ecx,nLoop start_loop : //开始循环 movaps xmm0,[esi] //xmm0 = [esi] mulps xmm0,xmm0 //xmm0 = xmm0 * xmm0 movaps xmm1,[edx] //xmm1 = [edx] mulps xmm1,xmm1 //xmm1 = xmm1 * xmm1 addps xmm0,xmm1 //xmm0 = xmm0 + xmm1 sqrtps xmm0,xmm0 //xmm0 = sqrt(xmm0) addps xmm0,xmm2 //xmm0 = xmm0 + xmm2 movaps [edi],xmm0 //[edi] = xmm0 add esi,16 //esi += 16 移动16个字节 add edx,16 //edx += 16 移动16个字节 add edi,16 //edi += 16 移动16个字节 dec ecx //ecx-- ecx -= 1 jnz 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 long unsigned __int8 m128i_u8[16]; //uchar unsigned __int16 m128i_u16[8]; //ushort unsigned __int32 m128i_u32[4]; //uint unsigned __int64 m128i_u64[2]; //ulonglong }__m128i; 

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

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_/article/details/ 

        数据类型转换系列。在做图像处理时,由于像素通道值是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; //Load a = _mm_loadu_ps(op1); b = _mm_loadu_ps(op2); //Calculate c = _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; //Load a = _mm_load_ps(op1); b = _mm_load_ps(op2); //Calculate c = _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指令集学习之旅(一)

    https://www.cnblogs.com/wangguchangqing/p/5466301.html

    https://www.cnblogs.com/zyl910/archive/2012/04/26/md00.html

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/156617.html

(0)
上一篇 2025-02-11 14:20
下一篇 2025-02-11 14:25

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信