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

原文地址:https://blog.csdn.net/yangdashi888/article/details/53376367

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++

 

 
  1. void CSSETestDlg::ComputeArrayCPlusPlus(

  2. float* pArray1, // [in] first source array

  3. float* pArray2, // [in] second source array

  4. float* pResult, // [out] result array

  5. int nSize) // [in] size of all arrays

  6. {

  7.  
  8. int i;

  9.  
  10. float* pSource1 = pArray1;

  11. float* pSource2 = pArray2;

  12. float* pDest = pResult;

  13.  
  14. for ( i = 0; i < nSize; i++ )

  15. {

  16. *pDest = (float)sqrt((*pSource1) * (*pSource1) + (*pSource2)

  17. * (*pSource2)) + 0.5f;

  18.  
  19. pSource1++;

  20. pSource2++;

  21. pDest++;

  22. }

  23. }


使用SSE内嵌原语

 

 
  1. void CSSETestDlg::ComputeArrayCPlusPlusSSE(

  2. float* pArray1, // [in] first source array

  3. float* pArray2, // [in] second source array

  4. float* pResult, // [out] result array

  5. int nSize) // [in] size of all arrays

  6. {

  7. int nLoop = nSize/ 4;

  8.  
  9. __m128 m1, m2, m3, m4;

  10.  
  11. __m128* pSrc1 = (__m128*) pArray1;

  12. __m128* pSrc2 = (__m128*) pArray2;

  13. __m128* pDest = (__m128*) pResult;

  14.  
  15.  
  16. __m128 m0_5 = _mm_set_ps1(0.5f); // m0_5[0, 1, 2, 3] = 0.5

  17.  
  18. for ( int i = 0; i < nLoop; i++ )

  19. {

  20. m1 = _mm_mul_ps(*pSrc1, *pSrc1); // m1 = *pSrc1 * *pSrc1

  21. m2 = _mm_mul_ps(*pSrc2, *pSrc2); // m2 = *pSrc2 * *pSrc2

  22. m3 = _mm_add_ps(m1, m2); // m3 = m1 + m2

  23. m4 = _mm_sqrt_ps(m3); // m4 = sqrt(m3)

  24. *pDest = _mm_add_ps(m4, m0_5); // *pDest = m4 + 0.5

  25.  
  26. pSrc1++;

  27. pSrc2++;

  28. pDest++;

  29. }

  30. }


使用SSE汇编

 

 
  1. void CSSETestDlg::ComputeArrayAssemblySSE(

  2. float* pArray1, // [输入] 源数组1

  3. float* pArray2, // [输入] 源数组2

  4. float* pResult, // [输出] 用来存放结果的数组

  5. int nSize) // [输入] 数组的大小

  6. {

  7. int nLoop = nSize/4;

  8. float f = 0.5f;

  9.  
  10. _asm

  11. {

  12. movss xmm2, f // xmm2[0] = 0.5

  13. shufps xmm2, xmm2, 0 // xmm2[1, 2, 3] = xmm2[0]

  14.  
  15. mov esi, pArray1 // 输入的源数组1的地址送往esi

  16. mov edx, pArray2 // 输入的源数组2的地址送往edx

  17.  
  18. mov edi, pResult // 输出结果数组的地址保存在edi

  19. mov ecx, nLoop //循环次数送往ecx

  20.  
  21. start_loop:

  22. movaps xmm0, [esi] // xmm0 = [esi]

  23. mulps xmm0, xmm0 // xmm0 = xmm0 * xmm0

  24.  
  25. movaps xmm1, [edx] // xmm1 = [edx]

  26. mulps xmm1, xmm1 // xmm1 = xmm1 * xmm1

  27.  
  28. addps xmm0, xmm1 // xmm0 = xmm0 + xmm1

  29. sqrtps xmm0, xmm0 // xmm0 = sqrt(xmm0)

  30.  
  31. addps xmm0, xmm2 // xmm0 = xmm1 + xmm2

  32.  
  33. movaps [edi], xmm0 // [edi] = xmm0

  34.  
  35. add esi, 16 // esi += 16

  36. add edx, 16 // edx += 16

  37. add edi, 16 // edi += 16

  38.  
  39. dec ecx // ecx--

  40. jnz start_loop //如果不为0则转向start_loop

  41. }

  42. }

 

 

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

获得信号能量

 
  1. /*

  2. * Compute Energy of a complex signal vector, removing the DC component!

  3. * input : points to vector

  4. * length : length of vector in complex samples

  5. */

  6.  
  7. #define shift 4

  8. #define shift_DC 0

  9.  
  10. int signal_energy(int *input, unsigned int length)

  11. {

  12. int i;

  13. int temp, temp2;

  14. register __m64 mm0, mm1, mm2, mm3;

  15. __m64 *in;

  16.  
  17. in = (__m64 *)input;

  18.  
  19. mm0 = _m_pxor(mm0,mm0);

  20. mm3 = _m_pxor(mm3,mm3);

  21.  
  22. for (i = 0; i < length >> 1; i++) {

  23. mm1 = in[i];

  24. mm2 = mm1;

  25. mm1 = _m_pmaddwd(mm1, mm1);

  26. mm1 = _m_psradi(mm1, shift);

  27. mm0 = _m_paddd(mm0, mm1);

  28. mm2 = _m_psrawi(mm2, shift_DC);

  29. mm3 = _m_paddw(mm3, mm2);

  30. }

  31.  
  32. mm1 = mm0;

  33. mm0 = _m_psrlqi(mm0, 32);

  34. mm0 = _m_paddd(mm0, mm1);

  35. temp = _m_to_int(mm0);

  36. temp /= length;

  37. temp <<= shift;

  38.  
  39. /*now remove the DC component*/

  40. mm2 = _m_psrlqi(mm3, 32);

  41. mm2 = _m_paddw(mm2, mm3);

  42. mm2 = _m_pmaddwd(mm2, mm2);

  43. temp2 = _m_to_int(mm2);

  44. temp2 /= (length * length);

  45. temp2 <<= (2 * shift_DC);

  46. temp -= temp2;

  47. _mm_empty();

  48. _m_empty();

  49.  
  50. return((temp > 0) ? temp : 1);

  51. }

 

 

 

 

引用的文章:

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

SSE优化一例

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

你可能感兴趣的:(C++,并行计算,C,指令集加速)