天天看點

SSE指令集入門

Intel公司的單指令多資料流式擴充(SSE,Streaming SIMD Extensions)技術能夠有效增強CPU浮點運算的能力。Visual Studio .NET 2003提供了對SSE指令集的程式設計支援,進而允許使用者在C++代碼中不用編寫彙編代碼就可直接使用SSE指令的功能。MSDN中有關SSE技術的主題[1]有可能會使不熟悉使用SSE彙編指令程式設計的初學者感到困惑,但是在閱讀MSDN有關文檔的同時,參考一下Intel軟體說明書(Intel Software manuals)[2]會使你更清楚地了解使用SSE指令程式設計的要點。

SIMD(single-instruction, multiple-data)是一種使用單道指令處理多道資料流的CPU執行模式,即在一個CPU指令執行周期内用一道指令完成處理多個資料的操作。考慮一下下面這個任務:計算一個很長的浮點型數組中每一個元素的平方根。實作這個任務的算法可以這樣寫:

for each  f in array        //對數組中的每一個元素

    f = sqrt(f)             //計算它的平方根

為了了解實作的細節,我們把上面的代碼這樣寫:

for each  f in array

{

    把f從記憶體加載到浮點寄存器

    計算平方根

    再把計算結果從寄存器中取出放入記憶體

}

具有Intel SSE指令集支援的處理器有8個128位的寄存器,每一個寄存器可以存放4個(32位)單精度的浮點數。SSE同時提供了一個指令集,其中的指令可以允許把浮點數加載到這些128位的寄存器之中,這些數就可以在這些寄存器中進行算術邏輯運算,然後把結果放回記憶體。采用SSE技術後,算法可以寫成下面的樣子:

for each  4 members in array  //對數組中的每4個元素

{

    把數組中的這4個數加載到一個128位的SSE寄存器中

    在一個CPU指令執行周期中完成計算這4個數的平方根的操作

    把所得的4個結果取出寫入記憶體

}

下面是一個示範的例子

使用純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);
}