SSE指令指令集进行程序加速、DCT的优化处理

1. 什么是SSE

说到SSE,首先要弄清楚的一个概念是SIMD(单指令多数据流,Single Instruction Multiple Data),是一种数据并行技术,能够在一条指令中同时对多个数据执行运算操作,增加处理器的数据吞吐量。SIMD特别的适用于多媒体应用等数据密集型运算。

  Intel公司的单指令多数据流式扩展(SSE,Streaming SIMD Extensions)技术能够有效增强CPU浮点运算的能力。Visual Studio .NET 2003提供了对SSE指令集的编程支持,从而允许用户在C++代码中不用编写汇编代码就可直接使用SSE指令的功能。

1.1 历史

1996年Intel首先推出了支持MMX的Pentium处理器,极大地提高了CPU处理多媒体数据的能力,被广泛地应用于语音合成、语音识别、音频视频编解码、图像处理和串流媒体等领域。但是MMX只支持整数运算,浮点数运算仍然要使用传统的x87协处理器指令。由于MMX与x87的寄存器相互重叠,在MMX代码中插入x87指令时必须先执行EMMS指令清除MMX状态,频繁地切换状态将严重影响性能。这限制了MMX指令在需要大量浮点运算的程序,如三维几何变换、裁剪和投影中的应用。
另一方面,由于x87古怪的堆栈式缓存器结构,使得硬件上将其流水线化和软件上合理调度指令都很困难,这成为提高x86架构浮点性能的一个瓶颈。为了解决以上这两个问题,AMD公司于1998年推出了包含21条指令的3DNow!指令集,并在其K6-2处理器中实现。K6-2是 第一个能执行浮点SIMD指令的x86处理器,也是第一个支持水平浮点寄存器模型的x86处理器。借助3DNow!,K6-2实现了x86处理器上最快的浮点单元,在每个时钟周期内最多可得到4个单精度浮点数结果,是传统x87协处理器的4倍。许多游戏厂商为3DNow!优化了程序,微软的DirectX 7也为3DNow!做了优化,AMD处理器的游戏性能第一次超过Intel,这大大提升了AMD在消费者心目中的地位。K6-2和随后的K6-III成为市场上的热门货。
1999年,随着Athlon处理器的推出,AMD为3DNow!增加了5条新的指令,用于增强其在DSP方面的性能,它们被称为“扩展3DNow!”(Extended 3DNow!)。
为了对抗3DNow!,Intel公司于1999年推出了SSE指令集。SSE几乎能提供3DNow!的所有功能,而且能在一条指令中处理两倍多的单精度浮点数;同时,SSE完全支持IEEE 754,在处理单精度浮点数时可以完全代替x87。这迅速瓦解了3DNow!的优势。
1999年后,随着主流操作系统和软件都开始支持SSE并为SSE优化,AMD在其2000年发布的代号为“Thunderbird”的Athlon处理器中添加了对SSE的完全支持(“经典”的Athlon或K7只支持SSE中与MMX有关的部分,AMD称之为“扩展MMX”即Extended MMX)。随后,AMD致力于AMD64架构的开发;在SIMD指令集方面,AMD跟随Intel,为自己的处理器添加SSE2和SSE3支持,而不再改进3DNow!。
2010年八月,AMD宣布将在新一代处理器中取消除了两条数据预取指令之外3DNow!指令的支持,并鼓励开发者将3DNow!代码重新用SSE实现。

1.2 MMX和SSE

MMX 是Intel在Pentium MMX中引入的指令集。其缺点是占用浮点数寄存器进行运算(64位MMX寄存器实际上就是浮点数寄存器的别名)以至于MMX指令和浮点数操作不能同时工作。为了减少在MMX和浮点数模式切换之间所消耗的时间,程序员们尽可能减少模式切换的次数,也就是说,这两种操作在应用上是互斥的。后来Intel在此基础上发展出SSE指令集;AMD在此基础上发展出3D Now指令集。
SSE(Streaming SIMD Extensions)是Intel在3D Now!发布一年之后,在PIII中引入的指令集,是MMX的超集。AMD后来在Athlon XP中加入了对这个指令集的支持。这个指令集增加了对8个128位寄存器XMM0-XMM7的支持,每个寄存器可以存储4个单精度浮点数。使用这些寄存器的程序必须使用FXSAVE和FXRSTR指令来保持和恢复状态。但是在PIII对SSE的实现中,浮点数寄存器又一次被新的指令集占用了,但是这一次切换运算模式不是必要的了,只是SSE和浮点数指令不能同时进入CPU的处理线而已。
SSE2是Intel在P4的最初版本中引入的,但是AMD后来在Opteron 和Athlon 64中也加入了对它的支持。这个指令集添加了对64位双精度浮点数的支持,以及对整型数据的支持,也就是说这个指令集中所有的MMX指令都是多余的了,同时也避免了占用浮点数寄存器。这个指令集还增加了对CPU的缓存的控制指令。AMD对它的扩展增加了8个XMM寄存器,但是需要切换到64位模式(AMD64)才可以使用这些寄存器。Intel后来在其EM64T架构中也增加了对AMD64的支持。
SSE3是Intel在P4的Prescott版中引入的指令集,AMD在Athlon 64的第五个版本中也添加了对它的支持。这个指令集扩展的指令包含寄存器的局部位之间的运算,例如高位和低位之间的加减运算;浮点数到整数的转换,以及对超线程技术的支持。

下面是一个演示的例子

使用纯C++

void CSSETestDlg::ComputeArrayCPlusPlus(  
          float* pArray1,                   // [in] first source array  
          float* pArray2,                   // [in] second source array  
          float* pResult,                   // [out] result array  
          int nSize)                        // [in] size of all arrays  
{  
  
    int i;  
  
    float* pSource1 = pArray1;  
    float* pSource2 = pArray2;  
    float* pDest = pResult;  
  
    for ( i = 0; i < nSize; i++ )  
    {  
        *pDest = (float)sqrt((*pSource1) * (*pSource1) + (*pSource2)  
                 * (*pSource2)) + 0.5f;  
  
        pSource1++;  
        pSource2++;  
        pDest++;  
    }  
}  

使用SSE内嵌原语

void CSSETestDlg::ComputeArrayCPlusPlusSSE(  
          float* pArray1,                   // [in] first source array  
          float* pArray2,                   // [in] second source array  
          float* pResult,                   // [out] result array  
          int nSize)                        // [in] size of all arrays  
{  
    int nLoop = nSize/ 4;  
  
    __m128 m1, m2, m3, m4;  
  
    __m128* pSrc1 = (__m128*) pArray1;  
    __m128* pSrc2 = (__m128*) pArray2;  
    __m128* pDest = (__m128*) pResult;  
  
  
    __m128 m0_5 = _mm_set_ps1(0.5f);        // m0_5[0, 1, 2, 3] = 0.5  
  
    for ( int i = 0; i < nLoop; i++ )  
    {  
        m1 = _mm_mul_ps(*pSrc1, *pSrc1);        // m1 = *pSrc1 * *pSrc1  
        m2 = _mm_mul_ps(*pSrc2, *pSrc2);        // m2 = *pSrc2 * *pSrc2  
        m3 = _mm_add_ps(m1, m2);                // m3 = m1 + m2  
        m4 = _mm_sqrt_ps(m3);                   // m4 = sqrt(m3)  
        *pDest = _mm_add_ps(m4, m0_5);          // *pDest = m4 + 0.5  
          
        pSrc1++;  
        pSrc2++;  
        pDest++;  
    }  
}  

使用SSE汇编

void CSSETestDlg::ComputeArrayAssemblySSE(  
          float* pArray1,                   // [输入] 源数组1  
          float* pArray2,                   // [输入] 源数组2  
          float* pResult,                   // [输出] 用来存放结果的数组  
          int nSize)                        // [输入] 数组的大小  
{  
    int nLoop = nSize/4;  
    float f = 0.5f;  
  
    _asm  
    {  
        movss   xmm2, f                         // xmm2[0] = 0.5  
        shufps  xmm2, xmm2, 0                   // xmm2[1, 2, 3] = xmm2[0]  
  
        mov         esi, pArray1                // 输入的源数组1的地址送往esi  
        mov         edx, pArray2                // 输入的源数组2的地址送往edx  
  
        mov         edi, pResult                // 输出结果数组的地址保存在edi  
        mov         ecx, nLoop                  //循环次数送往ecx  
  
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 = xmm1 + xmm2  
  
        movaps      [edi], xmm0                 // [edi] = xmm0  
  
        add         esi, 16                     // esi += 16  
        add         edx, 16                     // edx += 16  
        add         edi, 16                     // edi += 16  
  
        dec         ecx                         // ecx--  
        jnz         start_loop                //如果不为0则转向start_loop  
    }  
}  

在信号处理中的实际应用(sse2):

获得信号能量

/* 
* Compute Energy of a complex signal vector, removing the DC component!  
* input  : points to vector 
* length : length of vector in complex samples 
*/  
  
#define shift 4  
#define shift_DC 0  
  
int signal_energy(int *input, unsigned int length)  
{  
    int i;  
    int temp, temp2;  
    register __m64 mm0, mm1, mm2, mm3;  
    __m64 *in;  
  
    in = (__m64 *)input;  
  
    mm0 = _m_pxor(mm0,mm0);  
    mm3 = _m_pxor(mm3,mm3);  
  
    for (i = 0; i < length >> 1; i++) {  
        mm1 = in[i];  
        mm2 = mm1;  
        mm1 = _m_pmaddwd(mm1, mm1);  
        mm1 = _m_psradi(mm1, shift);  
        mm0 = _m_paddd(mm0, mm1);  
        mm2 = _m_psrawi(mm2, shift_DC);  
        mm3 = _m_paddw(mm3, mm2);  
    }  
  
    mm1 = mm0;  
    mm0 = _m_psrlqi(mm0, 32);  
    mm0 = _m_paddd(mm0, mm1);  
    temp = _m_to_int(mm0);  
    temp /= length;  
    temp <<= shift;   
  
    /*now remove the DC component*/  
    mm2 = _m_psrlqi(mm3, 32);  
    mm2 = _m_paddw(mm2, mm3);  
    mm2 = _m_pmaddwd(mm2, mm2);  
    temp2 = _m_to_int(mm2);  
    temp2 /= (length * length);  
    temp2 <<= (2 * shift_DC);  
    temp -= temp2;  
    _mm_empty();  
    _m_empty();  
  
    return((temp > 0) ? temp : 1);  
}  




引用的文章:

矩阵转置的SSE汇编优化艺术以及ARM cortext 汇编优化

SSE优化一例

DCT的优化处理解释和源代码==好






  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在MATLAB中,使用dct2函数可以对图像进行DCT处理。具体步骤如下: 1. 加载图像文件 使用imread函数加载需要处理的图像文件。例如,假设需要处理的图像文件名为“lena.bmp”,可以使用以下语句加载该文件: ```matlab img = imread('lena.bmp'); ``` 2. 转换为灰度图像 如果需要对彩色图像进行DCT处理,需要先将其转换为灰度图像。可以使用rgb2gray函数将彩色图像转换为灰度图像。例如,可以使用以下语句将img转换为灰度图像: ```matlab gray_img = rgb2gray(img); ``` 3. 进行DCT处理 使用dct2函数对灰度图像进行DCT处理。例如,可以使用以下语句对gray_img进行DCT处理: ```matlab dct_img = dct2(gray_img); ``` 4. 可视化DCT系数 将DCT系数可视化可以帮助我们更好地理解DCT处理过程。可以使用imshow函数将dct_img进行可视化。需要注意的是,由于DCT系数中包含了负值,因此需要对dct_img进行一些调整才能正确显示。以下是一个简单的调整方法: ```matlab dct_img = log(abs(dct_img)+1); dct_img = dct_img/max(dct_img(:)); imshow(dct_img); ``` 这样,就可以将DCT系数可视化了。 5. 逆DCT处理 如果需要将DCT处理后的图像恢复为原始图像,需要进行DCT处理。可以使用idct2函数对dct_img进行DCT处理。例如,可以使用以下语句对dct_img进行DCT处理: ```matlab recovered_img = idct2(dct_img); ``` 6. 显示恢复后的图像 可以使用imshow函数将recovered_img进行可视化,以显示恢复后的图像。例如,可以使用以下语句将recovered_img进行可视化: ```matlab imshow(recovered_img); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值