在C/C++代碼中使用SSE等指令集的指令(1)介紹

來源:http://blog.csdn.net/gengshenghong/article/details/7007100

我們知道,在C/C++代碼中,可以插入彙編代碼提高性能。現在的指令集有了很多的高級指令,如果我們希望使用這些高級指令來實現一些高效的算法,就可以在代碼中嵌入彙編,使用SSE等高級指令,這是可行的,但是如果對彙編不太熟悉,不願意使用匯編的人來說,其實也是可以的,這就是Compiler Intrinsics(http://msdn.microsoft.com/zh-cn/site/26td21ds)。

PS:下面的內容以Windows平臺爲主,對於Linux下,也有類似的方法。

(1)什麼是Intrinsics

Intrinsics是對MMX、SSE等指令集的指令的一種封裝,以函數的形式提供,使得程序員更容易編寫和使用這些高級指令,在編譯的時候,這些函數會被內聯爲彙編,不會產生函數調用的開銷。在理解intrinsics指令之前,先理解intrinsics函數。

(3)#pragma intrinsic和#pragma function

#pragma intrinsic(function[,function][,function]...):表示後面的函數將進行intrinsic,替換爲內部函數,去掉了函數調用的開銷,注意:有些地方解釋爲內聯,但是和內聯並不完全相同,對於內聯,可以指定任意函數爲內聯,但是此pragma intrinsic只能適用於編譯器規定的一部分函數,不是所有函數都能使用,而且,inline關鍵字一般用於指定自定義的函數,intrinsic則是系統庫函數的一部分。參考http://technet.microsoft.com/zh-cn/library/tzkfha43.aspx獲取詳細的說明。

下面分析這個例子:

  1. #include <math.h>  
  2. void foo()  
  3. {  
  4.     double var = cos(10);  
  5. }  
使用VS2010的32bit的command line編譯:

cl /c test.c /FA

輸出得到其彙編文件:

  1. ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01   
  2.   
  3.     TITLE   C:\tempLab\test.c  
  4.     .686P  
  5.     .XMM  
  6.     include listing.inc  
  7.     .model  flat  
  8.   
  9. INCLUDELIB LIBCMT  
  10. INCLUDELIB OLDNAMES  
  11.   
  12. PUBLIC  __real@4024000000000000  
  13. PUBLIC  _foo  
  14. EXTRN   _cos:PROC  
  15. EXTRN   __fltused:DWORD  
  16. ;   COMDAT __real@4024000000000000  
  17. ; File c:\templab\test.c  
  18. CONST   SEGMENT  
  19. __real@4024000000000000 DQ 04024000000000000r   ; 10  
  20. ; Function compile flags: /Odtp  
  21. CONST   ENDS  
  22. _TEXT   SEGMENT  
  23. _var$ = -8                      ; size = 8  
  24. _foo    PROC  
  25. ; Line 3  
  26.     push    ebp  
  27.     mov ebp, esp  
  28.     sub esp, 8  
  29. ; Line 4  
  30.     sub esp, 8  
  31.     fld QWORD PTR __real@4024000000000000  
  32.     fstp    QWORD PTR [esp]  
  33.     call    _cos  
  34.     add esp, 8  
  35.     fstp    QWORD PTR _var$[ebp]  
  36. ; Line 5  
  37.     mov esp, ebp  
  38.     pop ebp  
  39.     ret 0  
  40. _foo    ENDP  
  41. _TEXT   ENDS  
  42. END  
可以看到,這裏調用了call _cos函數進行運算,下面代碼修改如下:

  1. #include <math.h>  
  2. #pragma intrinsic(cos)  
  3. void foo()  
  4. {  
  5.     double var = cos(10);  
  6. }  
同樣的命令編譯,得到彙編如下:

  1. ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01   
  2.   
  3.     TITLE   C:\tempLab\test.c  
  4.     .686P  
  5.     .XMM  
  6.     include listing.inc  
  7.     .model  flat  
  8.   
  9. INCLUDELIB LIBCMT  
  10. INCLUDELIB OLDNAMES  
  11.   
  12. PUBLIC  __real@4024000000000000  
  13. PUBLIC  _foo  
  14. EXTRN   __fltused:DWORD  
  15. EXTRN   __CIcos:PROC  
  16. ;   COMDAT __real@4024000000000000  
  17. ; File c:\templab\test.c  
  18. CONST   SEGMENT  
  19. __real@4024000000000000 DQ 04024000000000000r   ; 10  
  20. ; Function compile flags: /Odtp  
  21. CONST   ENDS  
  22. _TEXT   SEGMENT  
  23. _var$ = -8                      ; size = 8  
  24. _foo    PROC  
  25. ; Line 4  
  26.     push    ebp  
  27.     mov ebp, esp  
  28.     sub esp, 8  
  29. ; Line 5  
  30.     fld QWORD PTR __real@4024000000000000  
  31.     call    __CIcos  
  32.     fstp    QWORD PTR _var$[ebp]  
  33. ; Line 6  
  34.     mov esp, ebp  
  35.     pop ebp  
  36.     ret 0  
  37. _foo    ENDP  
  38. _TEXT   ENDS  
  39. END  
對比之後,它們的主要區別的代碼段如下:

  1. sub esp, 8  
  2.     fld QWORD PTR __real@4024000000000000  
  3.   
  4.     fstp    QWORD PTR [esp]  
  5.     call    _cos  
  6.     add esp, 8  
  1. fld QWORD PTR __real@4024000000000000  
  2. call    __CIcos  
顯然,使用了Intrinsics之後的cos函數的指令少了很多,其調用的內部函數是_CIcos(http://msdn.microsoft.com/zh-cn/library/ff770589.aspx),此函數會計算對棧頂的元素直接進行cos運算,所以節省了很多函數調用參數傳遞等的指令。

仍然參考MSDN(http://technet.microsoft.com/zh-cn/library/tzkfha43.aspx)可以看到其中一段話:

The floating-point functions listed below do not have true intrinsic forms. Instead they have versions that pass arguments directly to the floating-point chip rather than pushing them onto the program stack.

當然,這是描述其中一部分Intrinsics函數的,Intrinsics也有不同的方式進行優化/內聯,具體參考MSDN查詢哪些函數可以使用Intrinsics以及是如何工作的(http://msdn.microsoft.com/zh-cn/site/26td21ds)。

#pragma function:使用格式和intrinsics一樣,pragma function用於指定函數不進行intrinsics操作,也就是不生成內部函數。

最後,要知道的一個內容是一個相關的編譯選項:/Oi

http://technet.microsoft.com/zh-cn/library/f99tchzc.aspx

/Oi 僅作爲對編譯器的請求,用於將某些函數調用替換爲內部函數;爲產生更好的性能,編譯器可能會調用函數(而不會將該函數調用替換爲內部函數)。

簡單的理解,就是告訴編譯器儘量使用intrinsics版本的調用,當然,最終的實際調用依賴於編譯器的判斷。

也可以參考wiki中(http://en.wikipedia.org/wiki/Intrinsic_function)關於intrinsic functions來幫助理解其作用。簡單來說,可以理解爲編譯器的“內置函數”,編譯器會根據情況進行一些優化。

(4)指令集相關的intrinsics介紹

上面介紹的是pragma對intrinsic函數的使用,其中介紹了cos,還有很多類似的“內置函數版本”。有時候將上面的這些稱之爲”intrinsics函數“,除此之外,intrinsics更廣泛的使用是指令集的封裝,能直接映射到高級指令集,從而使得程序員可以以函數調用的方式來實現彙編能達到的功能,編譯器會生成爲對應的SSE等指令集彙編。

1. 如何使用這類函數

在windows上,包含#include <**mmintrin.h>頭文件即可(不同的指令集擴展的函數可能前綴不一樣),也可以直接包含#include <intrin.h>(這裏面會根據使用環境判斷使用ADM的一些兼容擴展)。

2. 關於數據類型

這些和指令集相關的函數,一般都有自己的數據類型,不能使用一般的數據類型傳遞進行計算,一般來說,MMX指令是__m64(http://msdn.microsoft.com/zh-cn/library/08x3t697(v=VS.90).aspx)類型的數據,SSE是__m128類型的數據等等。

3. 函數名:

這類函數名一般以__m開頭。函數名稱和指令名稱有一定的關係。

4. 加法實例:

下面使用SSE指令集進行加法運算,一條指令對四個浮點數進行運算:

  1. #include <stdio.h>  
  2. #include <intrin.h>  
  3.   
  4. int main(int argc, char* argv[])  
  5. {  
  6.     __m128  a;  
  7.     __m128  b;  
  8.       
  9.     a = _mm_set_ps(1,2,3,4);        // Assign value to a  
  10.     b = _mm_set_ps(1,2,3,4);        // Assign value to a  
  11.   
  12.     __m128 c = _mm_add_ps(a, b);    // c = a + b  
  13.   
  14.     printf("0: %lf\n", c.m128_f32[0]);  
  15.     printf("1: %lf\n", c.m128_f32[1]);  
  16.     printf("2: %lf\n", c.m128_f32[2]);  
  17.     printf("3: %lf\n", c.m128_f32[3]);  
  18.   
  19.     return 0;  
  20. }  
從代碼看,好像很複雜,但是生成的彙編的效率會比較高。一條指令就完成了四個浮點數的加法,其運行結果如下:


(5)總結:

1. Intrinsics函數:能提高性能,會增大生成代碼的大小,是編譯器的”內置函數“。

2. Intrinsics對指令的封裝函數:直接映射到彙編指令,能簡化彙編代碼的編寫,另外,隱藏了寄存器分配和調度等。由於涉及到的數據類型、函數等內容較多,這裏只是一個簡單的介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章