深入探讨用位掩码代替分支(8):SSE指令集速度测试
在上一篇測試了MMX指令集,這次我們來測試SSE指令集。說的更精確一點,是測試SSE2指令集。
本篇致力于解決以下問題——
1.SSE/SSE2指令集是什么?
2.如何閱讀Intel/AMD的手冊?
3.如何運用SSE指令集?如何將MMX代碼升級為SSE代碼。
4.如何在VC++6.0這樣的高級語言編譯器中使用MMX指令集?
一、簡介
1999 年 Intel 推出了第 1 代的 SSE(Streaming SIMD Extensions)指令以回擊 AMD 的 3DNow! 指令,使用在 Pentium III 處理器上。隨后 AMD 在 2001 年 10 月 發布 的 Athlon XP 處理器上首次加入了 SSE 指令集。
2001 年 Intel 推出第 2 個版本的 SSE 指令,使用在 Pentium 4 處理器上,AMD 在 2003 年推出的 Athlon 64 和 Opteron 處理器上加入對 SSE2 指令的支持。
SSE有很多后續版本,詳見——
http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html
[x86]SIMD指令集發展歷程表(MMX、SSE、AVX等)
1.1 概述
SSE技術對x86體系的編程環境的擴展是——
1.8個128位的SSE寄存器(xmm0~xmm7)。?// 64位環境下增加到16個寄存器(xmm0~xmm15)。
2.SSE數據類型(緊縮單精度浮點)。而在SSE2中,又增加了整數和雙精度浮點類型。
3.SSE指令系統。
1.2 SSE寄存器
SSE寄存器集是由8個128位寄存器組成,見下圖。SSE指令使用寄存器名xmm0~xmm7直接訪問SSE寄存器。
還有一個新的控制/狀態寄存器MXCSR,用于屏蔽/開放數值異常處理、設置舍入方式、設置清零方式和觀察狀態標志。
與之前的MMX或3DNow!不同,這些寄存器并不是原來己有的寄存器(MMX和3DNow!均是使用x87浮點數寄存器),所以不需要像MMX或3DNow!一樣,要使用x87指令之前,需要利用一個EMMS指令來清除寄存器的狀態。因此,不像MMX或3DNow!指令,SSE運算指令,可以很自由地和x87浮點指令,或是MMX指令共用。
2003年,AMD發表的x86-64 延伸架構,為SSE技術增加了8個寄存器,共16個寄存器(xmm0~xmm15)。
(Figure 4-1. SSE Registers。出自AMD手冊 Vol.1 112)
1.3 SSE數據類型
SSE技術定義了以下數據類型——
1.128位緊縮單精度浮點(128-Bit Packed Single-Precision Floating-Point):4個單精度浮點(single)緊縮成一個128位。
(Figure 10-4. 128-Bit Packed Single-Precision Floating-Point Data Type。出自Intel手冊 Vol.1 10-8)
而在SSE2中,又定義了以下5種數據類型——
1.128位緊縮雙精度浮點(128-Bit Packed Double-Precision Floating-Point):2個雙精度浮點(double)緊縮成一個128位。
2.128位緊縮字節(128-Bit Packed Byte Integers):16個字節(byte)緊縮成一個128位。
3.128位緊縮字(128-Bit Packed Word Integers):8個字(word)緊縮成一個128位。
4.128位緊縮雙字(128-Bit Packed Doubleword Integers):4個雙字(doubleword)緊縮成一個128位。
5.128位緊縮四字(128-Bit Packed Quadword Integers):2個四字(quadword)緊縮成一個128位。
(Figure 11-2. Data Types Introduced with the SSE2 Extensions。出自Intel手冊 Vol.1 11-5)
因為“將64位像素轉為32位像素”這項工作需要字節(byte)和字(word),所以只有SSE2能滿足需求。
二、指令解讀
對于PACKUSWB指令來說,操作數不僅可以是MMX寄存器(出自MMX指令集),也可以是SSE寄存器(出自SSE2指令集),甚至可以是AVX寄存器(出自AVX指令集。本文不討AVX指令集)。雖然實際的機器碼有所不同。
對于這一點,Intel手冊的編排方案是——以指令名為準,然后再在那一節內分別介紹不同寄存器的效果。
而AMD的手冊不一樣,它按寄存器長度分成兩個文檔——
1.SSE、AVS指令在第4卷中(AMD64 Architecture Programmer's Manual Volume 4: 128-bit and 256 bit media instructions)。
2.MMX、3DNow!、浮點指令在第5卷中(AMD64 Architecture Programmer's Manual Volume 5: 64-Bit Media and x87 Floating-Point Instructions)。
2.1 Intel手冊原文
略。與前一篇(http://www.cnblogs.com/zyl910/archive/2012/04/09/noifopex7.html)的“2.1 Intel手冊對PACKUSWB指令的說明”相同。
PS:SSE編程指南見“Volume 1: Basic Architecture”的“Chapter 10 Programming with Streaming SIMD Extensions (SSE)”和“Chapter 11 Programming with Streaming SIMD Extensions 2 (SSE2)”。不屬于本文范疇,讀者可自行翻閱。
2.2 AMD手冊原文
找到AMD手冊的第4卷(AMD64 Architecture Programmer's Manual Volume 4: 128-bit and 256 bit media instructions)。如果沒有的話,請在官網下載——
http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals
打開第4卷(26568_APM_v4.pdf)。在左側的書簽樹中依次展開“2 Instruction Reference”,然后拖動滾動條找到“PACKUSWB”——
圖4
AMD手冊對PACKUSWB指令的說明有兩頁,上圖(圖4)是第一頁的內容。第二頁內容不屬于本文范疇,故不貼圖,讀者可自行翻閱。
PS:SSE編程指南見“Volume 1: Application Programming”的“4 Streaming SIMD Extensions Media and Scientific Programming”。不屬于本文范疇,讀者可自行翻閱。
2.3 手冊解讀
首先看Intel手冊,圖1最上面的那個方框內,列出了PACKUSWB指令在不同環境下(MMX、SSE、AVX)的效果。
本篇只關心SSE指令集。此時該指令的格式為“PACKUSWB xmm1, xmm2/m128”,描述信息為“Converts 8 signed word integers from xmm1 and 8 signed word integers from xmm2/m128 into 16 unsigned byte integers in xmm1 using unsigned saturation.”。
而在AMD手冊(圖4)中,對該指令的描述是“Converts 16-bit signed integers in xmm1 and xmm2 or mem128 into 8-bit signed integers with saturation. Writes packed results to xmm1.”。
在Intel手冊PACKUSWB指令的第二頁(圖2)中,有解釋該指令功能的偽代碼——
PACKUSWB (with 128-bit operands) DEST[7:0]← SaturateSignedWordToUnsignedByte (DEST[15:0]); DEST[15:8] ← SaturateSignedWordToUnsignedByte (DEST[31:16]); DEST[23:16] ← SaturateSignedWordToUnsignedByte (DEST[47:32]); DEST[31:24] ← SaturateSignedWordToUnsignedByte (DEST[63:48]); DEST[39:32] ← SaturateSignedWordToUnsignedByte (DEST[79:64]); DEST[47:40] ← SaturateSignedWordToUnsignedByte (DEST[95:80]); DEST[55:48] ← SaturateSignedWordToUnsignedByte (DEST[111:96]); DEST[63:56] ← SaturateSignedWordToUnsignedByte (DEST[127:112]); DEST[71:64] ← SaturateSignedWordToUnsignedByte (SRC[15:0]); DEST[79:72] ← SaturateSignedWordToUnsignedByte (SRC[31:16]); DEST[87:80] ← SaturateSignedWordToUnsignedByte (SRC[47:32]); DEST[95:88] ← SaturateSignedWordToUnsignedByte (SRC[63:48]); DEST[103:96] ← SaturateSignedWordToUnsignedByte (SRC[79:64]); DEST[111:104] ← SaturateSignedWordToUnsignedByte (SRC[95:80]); DEST[119:112] ← SaturateSignedWordToUnsignedByte (SRC[111:96]); DEST[127:120] ← SaturateSignedWordToUnsignedByte (SRC[127:112]);
該偽代碼的大致含義為——將DEST中的 每16位的帶符號整數 飽和轉換為 8位的無符號整數,放到返回值(DEST)的低64位;將SRC中的每16位的整數 飽和轉換為 8位的整數,放到返回值的高64位。
注:x86指令的2操作數指令一般是——第1個參數是DEST,第2個參數是SRC。即參數格式為“PACKUSWB DEST, SRC”。
2.4 畫圖解釋
用文字或偽代碼來解釋MMX指令都不太直觀,用圖片就直觀多了。
這一次AMD的手冊沒有配圖。
因此,我畫了一張圖片,更能清晰表示SSE2模式下PACKUSWB指令的功能——
該圖的風格與第7篇(http://www.cnblogs.com/zyl910/archive/2012/04/09/noifopex7.html)的圖片類似,左側是內存中的源數據,右側是運算結果,中間是SSE寄存器,箭頭代表運算過程。該圖 繪有三種操作——
1.加載(紅色箭頭)。將內存中的源數據(源緩沖區)加載到SSE寄存器。因為SSE寄存器是128位(16字節)的,所以該環節共加載了32字節數據,分別加載到2個SSE寄存器中(PACKUSWB需要兩個操作數)。
2.運算(綠色將頭)。這里就是PACKUSWB指令的功能,將 每個16位的帶符號整數 飽和轉換為 8位的無符號整數。
3.存儲(藍色箭頭)。將SSE寄存器中的運算結果 存儲到內存(目標緩沖區)。
三、如何在VC中使用MMX指令集?
對于Visual C++ 6.0來說,依次打上SP5、PP5補丁后,就能支持MMX、SSE、SSE2這三套指令集。它們的下載地址是——
SP5(Visual Studio 6.0 Service Pack 5):http://www.microsoft.com/download/en/details.aspx?id=2618
PP5(Visual C++ 6.0 Processor Pack):http://msdn.microsoft.com/en-us/library/aa718349.aspx
對于更高版本Visual Studio,它們內置了對MMX指令集的支持,不需要安裝補丁。詳見——
http://www.cnblogs.com/zyl910/archive/2012/02/28/vs_intrin_table.html
Intrinsics頭文件與SIMD指令集、Visual Studio版本對應表
3.1 使用內嵌匯編
在VC中,最直接的辦法就是使用內嵌匯編,即利用“_asm”關鍵字直接寫匯編語句。
例如下面那段代碼,先嘗試執行cpuid指令檢查CPU特性,再嘗試執行xorps指令測試系統中是否能運行SSE指令——
http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html
[VC6] 檢查MMX和SSE系列指令集的支持級別(最高SSE4.2)
3.2 使用Intrinsics函數
SSE指令也有對應的Intrinsics函數。
在MSDN中可以找到SSE/SSE2的Intrinsics函數幫助:http://msdn.microsoft.com/en-us/library/y0dh78ez(v=vs.110).aspx。具體的目錄層次是——
MSDN Library
Development Tools and Languages
Visual Studio 11 Beta
Visual C++? Reference
C/C++ Languages
Compiler Intrinsics
MMX, SSE, and SSE2 Intrinsics
SSE/SSE2的Intrinsics函數的應用方法,推薦Alex Farber的《Introduction to SSE Programming》——
http://www.codeproject.com/Articles/4522/Introduction-to-SSE-Programming
Introduction to SSE Programming
中文翻譯版見——
http://dev.gameres.com/Program/Other/sseintro.htm
基于SSE指令集的程序設計簡介
四、實際應用
現在我們想使用PACKUSWB指令,怎么知道該指令對應的Intrinsics函數呢?
最簡單的辦法就是查閱Intel手冊。在Intel手冊PACKUSWB指令的第三頁(圖3),列出Intrinsic函數的名稱——
因現在是探討SSE指令集,所以應該選用_mm_packus_epi16函數。
現在萬事具備,可以完成SSE版的“將64位像素轉為32位像素”函數了——
// 飽和處理SSE版 void f5_sse(BYTE* pbufD, const signed short* pbufS, int cnt) {//const signed short* pS = pbufS;//BYTE* pD = pbufD;const __m128i* pS = (const __m128i*)pbufS;__m128i* pD = (__m128i*)pbufD;int i;for(i=0; i<cnt; i+=4){// 同時對四個像素做飽和處理。即 將四個64位像素(4通道,每分量為帶符號16位) 轉為 四個32位像素(每分量為無符號8位)。pD[0] = _mm_packus_epi16(pS[0], pS[1]); // 飽和方式數據打包(帶符號16位->無符號8位)。等價于 for(i=0;i<8;++i){ pD[0].uB[i]=SU(pS[0].iW[i]); r.uB[8+i]=SU(pS[1].iW[i]); }// nextpS += 2;pD += 1;} }?
因SSE2版PACKUSWB指令(_mm_packus_epi16)的功能,現在的內循環變得十分簡單,一次就能處理4個像素。所以每次循環時“i+=4”。
注意這里將pbufS、pbufD這兩個指針均設定為__m128i指針類型。所以“pD += 1”實際上將指針地址前移了16個字節,而“pS += 2”將指針地址前移了32個字節。
五、全部代碼
全部代碼——
// MMX, SSE, SSE2 #include <emmintrin.h>// 用位掩碼做飽和處理.用求負生成掩碼 #define LIMITSU_FAST(n, bits) ( (n) & -((n) >= 0) | -((n) >= (1<<(bits))) ) #define LIMITSU_SAFE(n, bits) ( (LIMITSU_FAST(n, bits)) & ((1<<(bits)) - 1) ) #define LIMITSU_BYTE(n) ((BYTE)(LIMITSU_FAST(n, 8)))// 用位掩碼做飽和處理.用帶符號右移生成掩碼 #define LIMITSW_FAST(n, bits) ( ( (n) | ((signed short)((1<<(bits)) - 1 - (n)) >> 15) ) & ~((signed short)(n) >> 15) ) #define LIMITSW_SAFE(n, bits) ( (LIMITSW_FAST(n, bits)) & ((1<<(bits)) - 1) ) #define LIMITSW_BYTE(n) ((BYTE)(LIMITSW_FAST(n, 8)))// 數據規模 #define DATASIZE 16384 // 128KB / (sizeof(signed short) * 4)// 緩沖區。SSE需要按128位對齊 __declspec(align(16)) signed short bufS[DATASIZE*4]; // 源緩沖區。64位的顏色(4通道,每通道16位) __declspec(align(16)) BYTE bufD[DATASIZE*4]; // 目標緩沖區。32位的顏色(4通道,每通道8位)// 測試時的函數類型 typedef void (*TESTPROC)(BYTE* pbufD, const signed short* pbufS, int cnt);// http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html // SSE系列指令集的支持級別. simd_sse_level 函數的返回值。 #define SIMD_SSE_NONE 0 // 不支持 #define SIMD_SSE_1 1 // SSE #define SIMD_SSE_2 2 // SSE2 #define SIMD_SSE_3 3 // SSE3 #define SIMD_SSE_3S 4 // SSSE3 #define SIMD_SSE_41 5 // SSE4.1 #define SIMD_SSE_42 6 // SSE4.2const char* simd_sse_names[] = {"None","SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2", };// 是否支持MMX指令集 BOOL simd_mmx() {const DWORD BIT_DX_MMX = 0x00800000; // bit 23 DWORD v_edx;// check processor support __try {_asm {mov eax, 1cpuidmov v_edx, edx}}__except (EXCEPTION_EXECUTE_HANDLER){return FALSE;}if ( v_edx & BIT_DX_MMX ){// check OS support __try {_asm{pxor mm0, mm0 // executing any MMX instruction emms}return TRUE;}__except (EXCEPTION_EXECUTE_HANDLER){}}return FALSE; }// 檢測SSE系列指令集的支持級別 int simd_sse_level() {const DWORD BIT_D_SSE = 0x02000000; // bit 25const DWORD BIT_D_SSE2 = 0x04000000; // bit 26const DWORD BIT_C_SSE3 = 0x00000001; // bit 0const DWORD BIT_C_SSSE3 = 0x00000100; // bit 9const DWORD BIT_C_SSE41 = 0x00080000; // bit 19const DWORD BIT_C_SSE42 = 0x00100000; // bit 20BYTE rt = SIMD_SSE_NONE; // result DWORD v_edx;DWORD v_ecx;// check processor support __try {_asm {mov eax, 1cpuidmov v_edx, edxmov v_ecx, ecx}}__except (EXCEPTION_EXECUTE_HANDLER){return SIMD_SSE_NONE;}if ( v_edx & BIT_D_SSE ){rt = SIMD_SSE_1;if ( v_edx & BIT_D_SSE2 ){rt = SIMD_SSE_2;if ( v_ecx & BIT_C_SSE3 ){rt = SIMD_SSE_3;if ( v_ecx & BIT_C_SSSE3 ){rt = SIMD_SSE_3S;if ( v_ecx & BIT_C_SSE41 ){rt = SIMD_SSE_41;if ( v_ecx & BIT_C_SSE42 ){rt = SIMD_SSE_42;}}}}}}// check OS support __try {_asm{xorps xmm0, xmm0 // executing any SSE instruction }}__except (EXCEPTION_EXECUTE_HANDLER){return SIMD_SSE_NONE;}return rt; }// 用if分支做飽和處理 void f0_if(BYTE* pbufD, const signed short* pbufS, int cnt) {const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分別對4個通道做飽和處理pD[0] = (pS[0]<0) ? 0 : ( (pS[0]>255) ? 255 : (BYTE)pS[0] );pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );pD[2] = (pS[2]<0) ? 0 : ( (pS[2]>255) ? 255 : (BYTE)pS[2] );pD[3] = (pS[3]<0) ? 0 : ( (pS[3]>255) ? 255 : (BYTE)pS[3] );// nextpS += 4;pD += 4;} }// 用min、max飽和處理 void f1_min(BYTE* pbufD, const signed short* pbufS, int cnt) {const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分別對4個通道做飽和處理pD[0] = min(max(0, pS[0]), 255);pD[1] = min(max(0, pS[1]), 255);pD[2] = min(max(0, pS[2]), 255);pD[3] = min(max(0, pS[3]), 255);// nextpS += 4;pD += 4;} }// 用位掩碼做飽和處理.用求負生成掩碼 void f2_neg(BYTE* pbufD, const signed short* pbufS, int cnt) {const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分別對4個通道做飽和處理pD[0] = LIMITSU_BYTE(pS[0]);pD[1] = LIMITSU_BYTE(pS[1]);pD[2] = LIMITSU_BYTE(pS[2]);pD[3] = LIMITSU_BYTE(pS[3]);// nextpS += 4;pD += 4;} }// 用位掩碼做飽和處理.用帶符號右移生成掩碼 void f3_sar(BYTE* pbufD, const signed short* pbufS, int cnt) {const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分別對4個通道做飽和處理pD[0] = LIMITSW_BYTE(pS[0]);pD[1] = LIMITSW_BYTE(pS[1]);pD[2] = LIMITSW_BYTE(pS[2]);pD[3] = LIMITSW_BYTE(pS[3]);// nextpS += 4;pD += 4;} }// 飽和處理MMX版 void f4_mmx(BYTE* pbufD, const signed short* pbufS, int cnt) {//const signed short* pS = pbufS;//BYTE* pD = pbufD;const __m64* pS = (const __m64*)pbufS;__m64* pD = (__m64*)pbufD;int i;for(i=0; i<cnt; i+=2){// 同時對兩個像素做飽和處理。即 將兩個64位像素(4通道,每分量為帶符號16位) 轉為 兩個32位像素(每分量為無符號8位)。pD[0] = _mm_packs_pu16(pS[0], pS[1]); // 飽和方式數據打包(帶符號16位->無符號8位)。等價于 for(i=0;i<4;++i){ pD[0].uB[i]=SU(pS[0].iW[i]); pD[0].uB[4+i]=SU(pS[1].iW[i]); }// nextpS += 2;pD += 1;}// MMX狀態置空 _mm_empty(); }// 飽和處理SSE版 void f5_sse(BYTE* pbufD, const signed short* pbufS, int cnt) {//const signed short* pS = pbufS;//BYTE* pD = pbufD;const __m128i* pS = (const __m128i*)pbufS;__m128i* pD = (__m128i*)pbufD;int i;for(i=0; i<cnt; i+=4){// 同時對四個像素做飽和處理。即 將四個64位像素(4通道,每分量為帶符號16位) 轉為 四個32位像素(每分量為無符號8位)。pD[0] = _mm_packus_epi16(pS[0], pS[1]); // 飽和方式數據打包(帶符號16位->無符號8位)。等價于 for(i=0;i<8;++i){ pD[0].uB[i]=SU(pS[0].iW[i]); r.uB[8+i]=SU(pS[1].iW[i]); }// nextpS += 2;pD += 1;} }// 進行測試 void runTest(char* szname, TESTPROC proc) {const int nLoop = 16; // 使用MMX/SSE指令時速度太快了,只好再多循環幾次int i,j,k;DWORD tm0, tm1; // 存儲時間for(i=1; i<=3; ++i) // 多次測試 {//tm0 = GetTickCount();tm0 = timeGetTime();// mainfor(k=1; k<=nLoop; ++k){for(j=1; j<=4000; ++j) // 重復運算幾次延長時間,避免計時精度問題 {proc(bufD, bufS, DATASIZE);}}// show//tm1 = GetTickCount() - tm0;tm1 = timeGetTime() - tm0;printf("%s[%d]:\t%.1f\n", szname, i, (double)tm1/nLoop);// check//if (1==i)//{// // 檢查結果// for(j=0; j<=16; ++j)// printf("[%d]:\t%d\t%u\n", j, bufS[j], bufD[j]);//} } }int main(int argc, char* argv[]) {int i; // 循環變量//printf("Hello World!\n");printf("== noif:VC6 SIMD ==");// 初始化 srand( (unsigned)time( NULL ) );for(i=0; i<DATASIZE*4; ++i){bufS[i] = (signed short)((rand()&0x1FF) - 128); // 使數值在 [-128, 383] 區間 }// 準備開始。可以將將進程優先級設為實時if (argc<=1){printf("<Press any key to continue>");getch();printf("\n");}// 進行測試//runTest("f0_if", f0_if);//runTest("f1_min", f1_min);//runTest("f2_neg", f2_neg);//runTest("f3_sar", f3_sar);if (simd_mmx()) runTest("f4_mmx", f4_mmx);if (simd_sse_level()>=SIMD_SSE_2) runTest("f5_sse", f5_sse);// 結束前提示if (argc<=1){printf("<Press any key to exit>");getch();printf("\n");}return 0; }
注意——
1.SSE需要按128位對齊。于是bufS、bufD的聲明中增加了“__declspec(align(16))”。
2.在調用測試函數時,需要先檢查是否支持相應的指令集——
六、測試結果
在32位winXP上的測試結果——
== noif:VC6 SIMD ==<Press any key to continue> f4_mmx[1]: 37.5 f4_mmx[2]: 37.3 f4_mmx[3]: 38.9 f5_sse[1]: 25.6 f5_sse[2]: 26.1 f5_sse[3]: 25.7?
在64位win7上的測試結果——
== noif:VC6 SIMD ==<Press any key to continue> f4_mmx[1]: 37.1 f4_mmx[2]: 37.1 f4_mmx[3]: 36.1 f5_sse[1]: 25.4 f5_sse[2]: 24.4 f5_sse[3]: 25.3?
硬件環境——
CPU:Intel Core i3-2310M, 2100 MHz
內存:DDR3-1066
?
參考文獻——
《Intel? 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. December 2011. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《AMD64 Architecture Programmer's Manual Volume 4: 128-bit and 256 bit media instructions》. December 2011. http://support.amd.com/us/Processor_TechDocs/26568_APM_v4.pdf
《Pentium Ⅱ/Ⅲ體系結構及擴展技術》。劉清森、馬鳴錦、吳灝等著。國防工業出版社,2000年7月。
《Introduction to SSE Programming》。Alex Farber 著。http://www.codeproject.com/Articles/4522/Introduction-to-SSE-Programming
《基于SSE指令集的程序設計簡介》(Introduction to SSE Programming)。?譯。http://dev.gameres.com/Program/Other/sseintro.htm
《Pentium III處理器的SSE入門》。Bipin Patwardhan著,foenix譯。http://www.vckbase.com/document/viewdoc/?id=322
《在C/C++代碼中使用SSE等指令集的指令(1)介紹》。gengshenghong著。http://blog.csdn.net/gengshenghong/article/details/7007100
《x86 指令集發展歷程》。mik著。http://www.mouseos.com/x64/SIMD/x86_ISA.html
?
源碼下載——
http://files.cnblogs.com/zyl910/noifVC6s.rar
(建議閱讀編譯器生成的匯編代碼,位于Release\noifVC6s.asm)
總結
以上是生活随笔為你收集整理的深入探讨用位掩码代替分支(8):SSE指令集速度测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python dry原则_关于Pytho
- 下一篇: linux 安装adobe字体,厚道舒适