深入探讨用位掩码代替分支(7):MMX指令集速度测试
前面我們測試了高級語言做飽和處理的性能。其實,對于這樣的大批量數據處理,使用SIMD(Single Instruction Multiple Data,單指令多數據流)技術能極大的提高性能。MMX指令集是目前x86平臺上覆蓋最廣的SIMD指令集,于是本文對它進行探討。
本文致力于解決以下問題——
1.MMX指令集是什么?
2.如何閱讀Intel/AMD的手冊?
2.如何運用MMX指令集?
3.如何在VC++6.0這樣的高級語言編譯器中使用MMX指令集?
一、MMX指令集簡介
MMX(Multi Media eXtension,多媒體擴展指令集)指令集是Intel公司于1996年推出的一項多媒體指令增強技術。MMX指令集中包括有57條多媒體指令,通過這些指令可以一次處理多個數據。
隨后不久,其他CPU廠商也加入對MMX指令集的支持,例如AMD從1997年的K6開始就支持MMX指令集。關于Intel和AMD對SIMD指令集的支持狀況,詳見——
http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html
[x86]SIMD指令集發展歷程表(MMX、SSE、AVX等)
1.1 概述
MMX技術對x86體系的編程環境的擴展是——
1.8個MMX寄存器(mm0~mm7)。
2.4種MMX數據類型(緊縮字節、緊縮字、緊縮雙字、四字)。
3.MMX指令系統。
1.2 MMX寄存器
MMX寄存器集是由8個64位寄存器組成,見下圖。MMX指令使用寄存器名mm0~mm7直接訪問MMX寄存器。這些寄存器只能用來對MMX數據類型進行數據計算,不能尋址存儲器。MMX指令中存儲器操作數的尋址仍使用標準的x86尋址方式和通用寄存器來進行。
(Figure 9-2. MMX Register Set。出自Intel手冊 Vol. 1 9-3)
這些寄存器是作為獨立寄存器來定義的。但是,它們是通過對FPU(Float Point Unit,浮點運算單元)寄存器堆棧(R0~R7)別名而來的。所以MMX指令與浮點指令不能混用(EMMS指令可清除MMX狀態)。
1.3 MMX數據類型
MMX技術定義了以下4種64位數據類型——
1.緊縮字節(Packed byte):8個字節(byte)緊縮成一個64位。
2.緊縮字(Packed word):4個字(word)緊縮成一個64位。
3.緊縮雙字(Packed doubleword):2個雙字(doubleword)緊縮成一個64位。
4.四字(Quadword):一個64位。
緊縮字節數據類型中,字節的編號為0~7,第0字節在該類型的低有效位(位0~7),第7字節在高有效位(位56~63)。
緊縮字數據類型中,字的編號為0~3,第0字在該類型的低有效位(位0~15),第3字在高有效位(位48~63)。
緊縮雙字數據類型中,雙字的編號為0~1,第0雙字在該類型的低有效位(位0~31),第1雙字在高有效位(位32~63)。
MMX指令可以用64位塊方式與存儲器進行數據傳送,也可以用32位塊方式與通用寄存器進行數據傳送。
注意,在對緊縮數據類型進行算術或邏輯操作時,MMX指令會對64位MMX寄存器中的字節、字、雙字進行并行操作。
對緊縮緊縮數據類型的字節、字、雙字進行操作時,這些數據可以是帶符號整型數據,也可以是無符號整型數據。
二、指令解讀
MMX指令集中有57條指令,它們具體的功能是什么呢?這需要閱讀Intel和AMD的手冊。
例如“將64位像素轉為32位像素”這項工作,可以使用PACKUSWB指令。在這以PACKUSWB指令為例,講解如何閱讀Intel和AMD的手冊。
2.1 Intel手冊對PACKUSWB指令的說明
Intel手冊可以在這下載——
http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
Intel手冊有三種下載包——
1個文件:Combined Volume Set of Intel? 64 and IA-32 Architectures Software Developer’s Manuals。
3個文件:3 Volume Set of Intel? 64 and IA-32 Architectures Software Developer’s Manuals。
7個文件:7 Volume Set of Intel? 64 and IA-32 Architectures Software Developer’s Manuals。
這3種隨便選擇一種都行。我喜歡“Combined Volume Set of Intel? 64 and IA-32 Architectures Software Developer’s Manuals”,因為只需要下載1個文件。
下載完成后,打開該pdf文檔。在左側的書簽樹中依次展開“Volume 2: Instruction Set Reference, A-Z”、“Chapter 4 Instruction Set Reference, M-Z”、“4.2 Instructions (M-Z)”,然后拖動滾動條找到“PACKUSWB-Pack with Unsigned Saturation”——
Intel手冊對PACKUSWB指令的說明有三頁,本文將這3頁命名為 圖1、圖2、圖3。
?
圖1:Vol. 2B 4-191
?
圖2:Vol. 2B 4-192
?
圖3:Vol. 2B 4-193
PS:MMX編程指南見“Volume 1: Basic Architecture”的“Chapter 9 Programming with Intel? MMX? Technology”。不屬于本文范疇,讀者可自行翻閱。
2.2 AMD手冊對PACKUSWB指令的說明
AMD手冊可以在這下載——
http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals
找到“Manuals”條目,發現AMD手冊是分為5卷——
雖然本文只用到第5卷(AMD64 Architecture Programmer's Manual Volume 5: 64-Bit Media and x87 Floating-Point Instructions),但建議讀者將這5卷都下載下載。
下載完成后,打開第5卷(26569_APM_v5.pdf)。在左側的書簽樹中依次展開“64-Bit Media Instruction Reference”,然后拖動滾動條找到“PACKUSWB”——
圖4
AMD手冊對PACKUSWB指令的說明有兩頁,上圖(圖4)是第一頁的內容。第二頁內容不屬于本文范疇,故不貼圖,讀者可自行翻閱。
PS:MMX編程指南見“Volume 1: Application Programming”的“5 64-Bit Media Programming”。不屬于本文范疇,讀者可自行翻閱。
2.3 手冊解讀
首先看Intel手冊,圖1最上面的那個方框內,列出了PACKUSWB指令在不同環境下(MMX、SSE、AVX)的效果。
本文只關心MMX指令集。此時該指令的格式為“PACKUSWB mm, mm/m64”,描述信息為“Converts 4 signed word integers from mm and 4 signed word integers from mm/m64 into 8 unsigned byte integers in mm using unsigned saturation.”。
而在AMD手冊(圖4)中,對該指令的描述是“Packs 16-bit signed integers in an MMX register and another MMX register or 64-bit memory location into 8-bit unsigned integers in an MMX register.”。
可見,Intel與AMD手冊對該指令的說明略有不同。這是因為該功能很難用自然語言描述。用偽代碼或圖片會好一點。
在Intel手冊PACKUSWB指令的第二頁(圖2)中,有解釋該指令功能的偽代碼——
PACKUSWB (with 64-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 SRC[15:0]; DEST[47:40] ← SaturateSignedWordToUnsignedByte SRC[31:16]; DEST[55:48] ← SaturateSignedWordToUnsignedByte SRC[47:32]; DEST[63:56] ← SaturateSignedWordToUnsignedByte SRC[63:48];
該偽代碼的大致含義為——將DEST中的 每16位的帶符號整數 飽和轉換為 8位的無符號整數,放到返回值(DEST)的低32位;將SRC中的每16位的整數 飽和轉換為 8位的整數,放到返回值的高32位。
注:x86指令的2操作數指令一般是——第1個參數是DEST,第2個參數是SRC。即參數格式為“PACKUSWB DEST, SRC”。
2.4 畫圖解釋
用文字或偽代碼來解釋MMX指令都不太直觀,用圖片就直觀多了。
在AMD手冊(圖4)中,就配有PACKUSWB指令的插圖——
因為AMD手冊嚴格根據參數順序將mmx1(DEST)放在mmx2(SRC)的左邊,造成該圖的線條的發生交叉。交叉的箭頭容易讓人迷惑,但實際上各個數據還是連續存儲的,并沒有交叉存儲(我以前也被它搞暈了,過了很久才明白過來)。
因此,我畫了一張圖片,更能清晰表示PACKUSWB指令的功能——
該圖的風格與第3章(http://www.cnblogs.com/zyl910/archive/2012/03/27/noifopex3.html)的圖片類似,左側是內存中的源數據,右側是運算結果,中間是MMX寄存器,箭頭代表運算過程。該圖 繪有三種操作——
1.加載(紅色箭頭)。將內存中的源數據(源緩沖區)加載到mmx寄存器。因為MMX寄存器是64位(8字節)的,所以該環節共加載了16字節數據,分別加載到2個mmx寄存器中(PACKUSWB需要兩個操作數)。
2.運算(綠色將頭)。這里就是PACKUSWB指令的功能,將 每個16位的帶符號整數 飽和轉換為 8位的無符號整數。
3.存儲(藍色箭頭)。將mmx寄存器中的運算結果 存儲到內存(目標緩沖區)。
觀察該圖會發現,PACKUSWB指令的功能正好與“將64位像素轉為32位像素”的功能需求相吻合。所以我們可以利用PACKUSWB指令來實現“將64位像素轉為32位像素”,并對比測試性能。
三、如何在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特性,再嘗試執行pxor指令測試系統中是否能運行MMX指令——
http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html
[VC6] 檢查MMX和SSE系列指令集的支持級別(最高SSE4.2)
3.2 使用Intrinsics函數
手工編寫匯編代碼是困難的、易錯的、不易維護的。而且由于現代處理器強大的指令級并行性能,有些時候你辛辛苦苦編寫的匯編代碼,還沒有編譯器生成的代碼速度快。
對于MMX指令來說,可以利用Intrinsics函數來避免手工編寫匯編代碼。
Intrinsics是對MMX、SSE等指令集的指令的一種封裝,以函數的形式提供,使得程序員更容易編寫和使用這些高級指令,在編譯的時候,這些函數會被內聯為匯編,不會產生函數調用的開銷
在MSDN中可以找到Intrinsics函數的幫助:http://msdn.microsoft.com/en-us/library/26td21ds(v=vs.110).aspx。具體的目錄層次是——
MSDN Library
Development Tools and Languages
Visual Studio 11 Developer Preview
Visual Studio
Visual Studio Languages
Visual C++
Visual C++ Reference
C/C++ Languages
Compiler Intrinsics
Intrinsics函數的應用方法,推薦Alex Farber的《Introduction to MMX Programming》——
http://www.codeproject.com/Articles/4505/Introduction-to-MMX-Programming
Introduction to MMX Programming
中文翻譯版見——
http://dev.gameres.com/Program/Other/mmxintro.htm
基于MMX指令集的程序設計簡介
四、實際應用
現在我們想使用PACKUSWB指令,怎么知道該指令對應的Intrinsics函數呢?
最簡單的辦法就是查閱Intel手冊。在Intel手冊PACKUSWB指令的第三頁(圖3),列出Intrinsic函數的名稱——
因現在是探討MMX指令集,所以應該選用_mm_packs_pu16函數。
現在萬事具備,可以完成MMX版的“將64位像素轉為32位像素”函數了——
// 飽和處理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(); }
因PACKUSWB指令(_mm_packs_pu16)的功能,現在的內循環變得十分簡單,一次就能處理2個像素。
注意這里將pbufS、pbufD這兩個指針均設定為__m64指針類型。所以“pD += 1”實際上將指針地址前移了8個字節,而“pS += 2”將指針地址前移了16個字節。
最后不要忘了執行EMMS指令清除MMX狀態,它對應的Intrinsics函數是_mm_empty。
五、改進測試方法
因MMX版的代碼運行速度非常快,為了保證測試結果的精度,增加了“for(k=1; k<=nLoop; ++k)”這一層循環,所以在計算運行時間時除以了nLoop——
// 進行測試 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]);//} } }
在調用MMX版測試函數時,需要先檢查是否支持MMX——
if (simd_mmx())?runTest("f4_mmx", f4_mmx);
六、測試結果
在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?
在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?
硬件環境——
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 5: 64-Bit Media and x87 Floating-Point Instructions》. December 2011. http://support.amd.com/us/Processor_TechDocs/26569_APM_v5.pdf
《INTEL 體系結構 MMX? 技術開發者手冊》。樊一鵬譯。http://dev.gameres.com/Program/Other/Fmmx/index.htm
《PC平臺新技術MMX 上冊 ——開發編程指南》。吳樂南等著。東南大學出版社,1997年10月。
《Pentium Ⅱ/Ⅲ體系結構及擴展技術》。劉清森、馬鳴錦、吳灝等著。國防工業出版社,2000年7月。
《編程高手箴言》。梁肇新著。電子工業出版社,2003年10月。
《Introduction to MMX Programming》。Alex Farber 著。http://www.codeproject.com/Articles/4505/Introduction-to-MMX-Programming
《基于MMX指令集的程序設計簡介》(Introduction to MMX Programming)。?譯。http://dev.gameres.com/Program/Other/mmxintro.htm
《在C/C++代碼中使用SSE等指令集的指令(1)介紹》。gengshenghong著。http://blog.csdn.net/gengshenghong/article/details/7007100
轉載于:https://www.cnblogs.com/zyl910/archive/2012/04/09/noifopex7.html
總結
以上是生活随笔為你收集整理的深入探讨用位掩码代替分支(7):MMX指令集速度测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何再发行 SAPI 5.1 核心组件
- 下一篇: 检测内存泄露