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,
- 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指令集的程式設計簡介 |
|||||||||||||||||
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++程式設計人員在使用SSE指令函式程式設計時不必關心這些128位的暫存器,你可以使用128位的資料型別“__m128”和一系列C++函式來實現這些算術和邏輯操作,而決定程式使用哪個SSE暫存器以及程式碼優化是C++編譯器的任務。當需要對很長的浮點數陣列中的元素進行處理的時候,SSE技術確實是一種很高效的方法。 SSE程式設計詳細介紹 包含的標頭檔案: 所有的SSE指令函式和__m128資料型別都在xmmintrin.h檔案中定義: #include <xmmintrin.h> 因為程式中用到的SSE處理器指令是由編譯器決定,所以它並沒有相關的.lib庫檔案。 資料分組(Data Alignment) 由SSE指令處理的每一個浮點數陣列必須把其中需要處理的數每16個位元組(128位二進位制)分為一組。一個靜態陣列(static array)可由__declspec(align(16))關鍵字宣告: __declspec(align(16)) float m_fArray[ARRAY_SIZE]; 動態陣列(dynamic array)可由_aligned_malloc函式為其分配空間: m_fArray = (float*) _aligned_malloc(ARRAY_SIZE * sizeof(float), 16); 由_aligned_malloc函式分配空間的動態陣列可以由_aligned_free函式釋放其佔用的空間: _aligned_free(m_fArray); __m128 資料型別 該資料型別的變數可用做SSE指令的運算元,它們不能被使用者指令直接存取。_m128型別的變數被自動分配為16個位元組的字長。 CPU對SSE指令集的支援 如果你的CPU能夠具有了SSE指令集,你就可以使用Visual Studio .NET 2003提供的對SSE指令集支援的C++函式庫了,你可以檢視MSDN中的一個Visual C++ CPUID的例子[4],它可以幫你檢測你的CPU是否支援SSE、MMX指令集或其它的CPU功能。 程式設計例項 以下講解了SSE技術在Visual Studio .NET 2003下的應用例項,你可以在http://www.codeproject.com/cpp/sseintro/SSE_src.zip下載示例程式壓縮包。該壓縮包中含有兩個專案,這兩個專案是基於微軟基本類庫(MFC)建立的Visual C++.NET專案,你也可以按照下面的講解建立這兩個專案。 SSETest 示例專案 SSETest專案是一個基於對話方塊的應用程式,它用到了三個浮點陣列參與運算: fResult[i] = sqrt( fSource1[i]*fSource1[i] + fSource2[i]*fSource2[i] ) + 0.5 其中i = 0, 1, 2 ... ARRAY_SIZE-1 其中ARRAY_SIZE被定義為30000。資料來源陣列(Source陣列)通過使用sin和cos函式給它賦值,我們用Kris Jearakul開發的瀑布狀圖表控制元件(Waterfall chart control)[3] 來顯示參與計算的源陣列和結果陣列。計算所需的時間(以毫秒ms為單位)在對話方塊中顯示出來。我們使用三種不同的途徑來完成計算: 純C++程式碼; 使用SSE指令函式的C++程式碼; 包含SSE彙編指令的程式碼。 純C++程式碼: void CSSETestDlg::ComputeArrayCPlusPlus( float* pArray1, // [輸入] 源陣列1 float* pArray2, // [輸入] 源陣列2 float* pResult, // [輸出] 用來存放結果的陣列 int nSize) // [輸入] 陣列的大小 { 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特性的C++程式碼重寫上面這個函式。為了查詢使用SSE指令C++函式的方法,我參考了Intel軟體說明書(Intel Software manuals)中有關SSE彙編指令的說明,首先我是在第一卷的第九章找到的相關SSE指令,然後在第二卷找到了這些SSE指令的詳細說明,這些說明有一部分涉及了與其特性相關的C++函式。然後我通過這些SSE指令對應的C++函式查找了MSDN中與其相關的說明。搜尋的結果見下表:
|