在C/C++程式碼中使用SSE等指令集的指令(1)介紹
我們知道,在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
下面分析這個例子:
#include <math.h>
void foo()
{
double var = cos(10);
}
使用VS2010的32bit的command line編譯:
cl /c test.c /FA
輸出得到其彙編檔案:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE C:\tempLab\test.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC [email protected]
PUBLIC _foo
EXTRN _cos:PROC
EXTRN __fltused:DWORD
; COMDAT [email protected]
; File c:\templab\test.c
CONST SEGMENT
[email protected] DQ 04024000000000000r ; 10
; Function compile flags: /Odtp
CONST ENDS
_TEXT SEGMENT
_var$ = -8 ; size = 8
_foo PROC
; Line 3
push ebp
mov ebp, esp
sub esp, 8
; Line 4
sub esp, 8
fld QWORD PTR [email protected]
fstp QWORD PTR [esp]
call _cos
add esp, 8
fstp QWORD PTR _var$[ebp]
; Line 5
mov esp, ebp
pop ebp
ret 0
_foo ENDP
_TEXT ENDS
END
可以看到,這裡呼叫了call_cos函式進行運算,下面程式碼修改如下:
#include <math.h>
#pragma intrinsic(cos)
void foo()
{
double var = cos(10);
}
同樣的命令編譯,得到彙編如下:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE C:\tempLab\test.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC [email protected]
PUBLIC _foo
EXTRN __fltused:DWORD
EXTRN __CIcos:PROC
; COMDAT [email protected]
; File c:\templab\test.c
CONST SEGMENT
[email protected] DQ 04024000000000000r ; 10
; Function compile flags: /Odtp
CONST ENDS
_TEXT SEGMENT
_var$ = -8 ; size = 8
_foo PROC
; Line 4
push ebp
mov ebp, esp
sub esp, 8
; Line 5
fld QWORD PTR [email protected]
call __CIcos
fstp QWORD PTR _var$[ebp]
; Line 6
mov esp, ebp
pop ebp
ret 0
_foo ENDP
_TEXT ENDS
END
對比之後,它們的主要區別的程式碼段如下:
sub esp, 8
fld QWORD PTR [email protected]
fstp QWORD PTR [esp]
call _cos
add esp, 8
fld QWORD PTR [email protected]
call __CIcos
顯然,使用了Intrinsics之後的cos函式的指令少了很多,其呼叫的內部函式是_CIcos(http://msdn.microsoft.com/zh-cn/library/ff770589.aspx),此函式會計算對棧頂的元素直接進行cos運算,所以節省了很多函式呼叫引數傳遞等的指令。
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
/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. 關於資料型別
3. 函式名:
這類函式名一般以__m開頭。函式名稱和指令名稱有一定的關係。
4. 加法例項:
下面使用SSE指令集進行加法運算,一條指令對四個浮點數進行運算:
#include <stdio.h>
#include <intrin.h>
int main(int argc, char* argv[])
{
__m128 a;
__m128 b;
a = _mm_set_ps(1,2,3,4); // Assign value to a
b = _mm_set_ps(1,2,3,4); // Assign value to a
__m128 c = _mm_add_ps(a, b); // c = a + b
printf("0: %lf\n", c.m128_f32[0]);
printf("1: %lf\n", c.m128_f32[1]);
printf("2: %lf\n", c.m128_f32[2]);
printf("3: %lf\n", c.m128_f32[3]);
return 0;
}
從程式碼看,好像很複雜,但是生成的彙編的效率會比較高。一條指令就完成了四個浮點數的加法,其執行結果如下:
(5)總結:
1. Intrinsics函式:能提高效能,會增大生成程式碼的大小,是編譯器的”內建函式“。
2. Intrinsics對指令的封裝函式:直接對映到彙編指令,能簡化彙編程式碼的編寫,另外,隱藏了暫存器分配和排程等。由於涉及到的資料型別、函式等內容較多,這裡只是一個簡單的介紹。
相關推薦
.NET/C# 在程式碼中測量程式碼執行耗時的建議(比較系統性能計數器和系統時間)
我們有很多種方法評估一個方法的執行耗時,比如使用效能分析工具,使用基準效能測試。不過傳統的在程式碼中編寫計時的方式依然有效,因為它可以生產環境或使用者端得到真實環境下的執行耗時。 如果你希望在 .NET/C# 程式碼中編寫計時,那麼閱讀本文可以獲得一些建議。閱讀本文也可以瞭解到 Qu
《WebGL程式設計指南》---從示例程式碼中學習WebGL之初識【1】
寫在前面 筆者希望學習WebGL很久了,但是鑑於種種的事一直沒有開展,恰好研究生複試過了,在這空檔時間段內想學一門新技術,於是想到了WebGL,經前同事推薦,果斷買了《WebGL程式設計指南》這本書,於是,我的學習之旅就開始了。對於程式設計人員來說,案例程式碼是最好的教科書
在C/C++程式碼中使用SSE等指令集的指令(3)SSE指令集基礎
相關參考: scalar packed (1)Summary: 前面瞭解到了可以在程式碼中使用intrinsics函式來實現類似彙編的高階指令集(SSE等)指令,在這裡,為了加深理解,再次分析一下SSE指令。 (2)MMX指令集 首先要提到MMX指令集,MMX指令集
在C/C++程式碼中使用SSE等指令集的指令(1)介紹
我們知道,在C/C++程式碼中,可以插入彙編程式碼提高效能。現在的指令集有了很多的高階指令,如果我們希望使用這些高階指令來實現一些高效的演算法,就可以在程式碼中嵌入彙編,使用SSE等高階指令,這是可行的,但是如果對彙編不太熟悉,不願意使用匯編的人來說,其實也是可以的,這就是Compiler Intrinsi
c/c++ 程式碼中使用sse指令集加速
使用SSE指令,首先要了解這一類用於進行初始化載入資料以及將暫存器的資料儲存到記憶體相關的指令, 我們知道,大多數SSE指令是使用的xmm0到xmm8的暫存器,那麼使用之前,就需要將資料從記憶體載入到這些暫存器。 1. load系列,用於載入資料,從記憶體到暫存器
SSE指令集 c,c++程式程式碼優化
基於SSE指令集的程式設計簡介 SSE技術簡介 Intel公司的單指令多資料流式擴充套件(SSE,Streaming SIMD Extensions)技術能夠有效增強CPU浮點運算的能力。
UE4 C++程式碼中使用材質,字型等資源
當我使用ue4 editor製作了一個材質(Material)或字型(Font)之後,該材質和字型等資源不僅可以在editor中使用,還可以在C++程式碼中使用。 下圖為我在editor中製作的資源: 我們可以通過右鍵點選該資源,點選“Copy Reference”來獲得資源的參考路徑。
visual studio編寫C#程式碼時“未能從程式集.....中載入型別”和“找不到方法”的一種可能的解決辦法
編譯前報錯:$exception {"未能從程式集“XSW.MySQLDAL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中載入型別“XSW.MySQLDAL.EnterpriseLibraryProd
如何在ubuntu中寫一個簡單的C語言程式碼並編譯執行
首先需要安裝一個編譯器 因為筆者也是剛剛開始學習ubuntu所以不知道各個編譯器之間的區別,筆者所用的是gcc就簡單介紹一下gcc的安裝方法吧。 方法一: 開啟控制檯輸入以下程式碼: sudo apt-get build-dep gcc; sudo apt-get bui
C/C++中的預編譯指令
地方 efi def 允許 兩種 常量 打印 string 運算符 程序的編譯過程可以分為預處理、編譯、匯編三部分,其中預處理是首先執行的過程,預處理過程掃描程序源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。預處理過程讀入源代碼之後,會檢查代碼裏包含的預處理指令,
C++中getline等輸入輸出函式的用法
此文轉自 在學習C++的過程中,經常會遇到輸入輸出的問題,以下總結一下下面幾個函式的用法: 1)、cin 2)、cin.get() 3)、cin.getline
封裝C#程式碼為DLL並在C#程式碼中引用
1.封裝C#程式碼為DLl 在VS2012中建立專案選擇類庫,命名testMyDll,新建類msg,注意修飾符必須為public using System; using System.Collections.Generic; using System.Linq; using System.T
JNI開發中在c程式碼中列印日誌
JNI開發在c程式碼中列印日誌 Android.mk檔案增加以下內容 LOCAL_LDLIBS += -llog C程式碼中增加以下內容#include <android/log.h> #define LOG_TAG "System.out" #define LOG
C/C++中#,##,__FILE__,__LINE__等的用法
#include <stdio.h> #define CAT(N) X ## N //## 在巨集定義中將兩個字元連線起來,構成一個新的識別符號 #define PRINTF_XN(n) printf("X" # n "=%d\n",X ## n); //# 將
單鏈表——求兩個集合的差集 A,B集合求差集放到C連結串列中
#include <stdio.h> #include <malloc.h> typedef struct Node { int data; struct Node *next; }LNode,*LinkList; void list
visual studio c++程式碼中使用git版本資訊
總體思路 本篇使用的方法不更改visual studio的任何設定,完全使用的是“蠻力”。 使用python寫成的tool獲取版本資訊,並auto code為一個.h檔案,檔案中僅是一個git版本資訊類 在需要git版本資訊的程式碼中,使用1中自動生成的類
.NET/C# 中你可以在程式碼中寫多個 Main 函式,然後按需要隨時切換
.NET/C# 程式從 Main 函式開始執行,基本上各種書籍資料都是這麼寫的。不過,我們可以寫多個 Main 函式,然後在專案檔案中設定應該選擇哪一個 Main 函式。 你可能會覺得這樣沒有什麼用,不過如果你的應用程式在不同的編譯條件下有不同的啟動程式碼,或者
C/C++中的預編譯指令(轉)
reference:https://blog.csdn.net/sunshinewave/article/details/51020421 程式的編譯過程可以分為預處理、編譯、彙編三部分,其中預處理是首先執行的過程,預處理過程掃描程式原始碼,對其進行初步的轉換,產生新的原始碼提供給編譯器。 預處理過程
常用的ARM彙編指令集與彙編呼叫C語言
***指令與偽指令: 指令:CPU機器指令的助記符,經過編譯後得到一串10組成的機器碼,可以被CPU直接讀取執行。 偽指令:編譯器環境提供,用來指導編譯過程,最終不會生成機器碼。 ***LDR/STR架構: CPU不能直接對記憶體的內容進行操作,必須藉助CPU
使用C#對MongoDB中的資料進行查詢,修改等操作
首先,使用的是官方提供的C#訪問元件https://github.com/mongodb/mongo-csharp-driver 然後、編譯後引用MongoDB.Bson.dll及MongoDB.Driver.dll,並在cs檔案中宣告引用 using MongoDB.