2011-05-14 70 views
1

假設我有三塊的代碼,可以調用本機運API如下:哪種方法可以比其他方法更快地調用native API?

1)編譯方式:/clr

#pragma unmanaged 
void finc(){ 
::MessageBox(NULL, "Hi all", "Win32 Message Box",0); 
} 
#pragma managed 

2)編譯方式:/clr:safe

[DllImport("user32.dll", CharSet=CharSet::Auto)] 
int MessageBox(IntPtr, String^ text, String^ caption, unsigned int type); 
int main(){ 
MessageBox(IntPtr::Zero, "Hi all", "Win32 Message Box",0); 
} 

3)編譯方式:/clr

void finc(){ 
::MessageBox(NULL, "Hi all", "Win32 Message Box",0); 
} 

哪個代碼可以給調用運API函數,why最佳性能(速度最快)?

+1

我會測試他們找出來的;) – Blender 2011-05-14 06:20:55

+1

浩神,託管C++!我的眼睛在流血...... – 2011-05-14 06:23:57

+0

這些是對Win32的調用,而不是對本機API的調用。如果它對性能產生真正的影響,我會很驚訝。如果我是你,我會擔心別的事情。 – 2011-05-14 06:40:05

回答

2

@csl是,你應該爲了個人資料,找出你的代碼的部分需要優化什麼權利。你應該在優化之後重新進行配置,以確保它有幫助。

這就是說,使用輪廓儀作爲Oracle是不優化的有效方式。一旦確定了導致性能問題的代碼,瞭解這些問題來自何處的更有可能導致您改進,而不是盲目地進行更改,並希望分析器能夠告訴您它變得更快。

一個優化的最根本的規則(配置文件後先)是,如果B方法做了所有同樣的工作方法的一個呢,它不會更快。如果CPU浪費時間等待不在高速緩存中的數據,但同時以相同的方式獲得相同的結果,則很有可能在同一時間內安裝額外的工作。

因此,我首先注意到您的示例,除了MessageBox API的功能遠遠超過設置函數調用的功能之外,因此您的儲蓄將微不足道,因爲必須完成某些操作才能調用消息框。

  • 參數必須按照調用約定推送。
  • 該功能必須被調用。

這導致瞭如下結論:

不可能使代碼比簡單的非託管的程序快:

#include <windows.h> 
void func(void) 
{ 
    ::MessageBoxA(NULL, "Hi all", "Win32 Message Box", MB_OK); 
} 

int main(void) 
{ 
    func(); 
} 

經過優化內聯調用,它肯定本地優化器是相當不錯的,除了準備參數列表和調用函數外,這個程序不會做任何事情,對吧?

嗯,這是不正確的。 C++運行時庫將執行一些設置,這對於此示例不是必需的。您可以通過使用自定義入口點獲得巨大改進,但它看起來只會更快,實際上這些更改來自編譯器隱藏的代碼。

即使消除了C++運行時庫,您仍然留下少量開銷:OS加載程序解析導入,這導致了另一個細節:此處調用是使用函數指針創建的,而不是立即地址。然而,現代CPU分支預測消除了這種間接性的代價。

這真的會成爲主題,順便說一句,在後臺添加的代碼管理。

無論如何,下一個案例。該代碼將不會快,但我懷疑這將是更慢或者,至少後主開始運行

// compile with /clr 
#include <windows.h> 

void func(void) 
{ 
    ::MessageBoxA(NULL, "Hi all", "Win32 Message Box", MB_OK); 
} 

int main(void) 
{ 
    func(); 
} 

這是使用C++互操作混合模式組件。 C++編譯器不能再調用func,但優化現在是JIT的責任。在這種情況下,C++/CLI編譯器也會關閉代碼安全性,如果沒有權限運行非託管代碼,甚至無法加載混合模式程序集,因此在每次調用時都不會檢查它。

這個程序比第一個程序要慢很多,因爲運行時庫的初始化因.NET的引入而變得非常複雜。但是在JIT運行之後,函數調用將與第一個相同,每個調用的func的開銷是相同的。

讓我們來看看從問題的另一個案例:

// compile with /clr 
#include <windows.h> 

#pragma managed(push, off) 
void func(void) 
{ 
    ::MessageBoxA(NULL, "Hi all", "Win32 Message Box", 0); 
} 
#pragma managed(pop) 

int main(void) 
{ 
    func(); 
} 

我們還是要加載.NET運行時,我們還是要JIT,我們還是要實際設置的參數並調用MessageBox。所以這個不能比上面的例子更快。由於func的實施不是託管代碼,所以JIT不能內聯它。但是現在C++編譯器可以這樣做,因爲func不再是必須單獨放置的託管函數。如果優化器內嵌func,我們獲得與前一種情況相同的性能,否則我們會有一個額外的函數調用。真的不是你應該擔心的事情。

下一頁情況:

// compile with /clr:pure 
#include <windows.h> 

void func(void) 
{ 
    ::MessageBoxA(NULL, "Hi all", "Win32 Message Box", MB_OK); 
} 

int main(void) 
{ 
    func(); 
} 

現在我們不再有一個混合模式組件和我們不再有C++互操作。相反,編譯器會生成一個匹配的p/invoke簽名(爲什麼你會用手寫p/invoke簽名?)。因此,它不是OS加載程序解析導入,而是.NET的運行時。而且現在JIT可以使用導入地址,理論上可以將固定地址直接編碼到調用指令中,並避免函數指針,儘管我們已經知道分支預測使這種差異沒有實際意義。但是p/invoke是爲了安全而設計的,而不是速度,所以你實際得到的是一些額外的堆棧檢查,以確保p/invoke簽名是正確的。底線:這個版本肯定比較慢,但不夠重要。

讓我們做一個很小的變化:

// compile with /clr:safe 
#include <windows.h> 

void func(void) 
{ 
    ::MessageBoxA(NULL, "Hi all", "Win32 Message Box", MB_OK); 
} 

int main(void) 
{ 
    func(); 
} 

好吧,請大會可覈查的。所有p/invoke內容與/clr:pure相同,但是此代碼不再侷限於在受信任的環境中運行。因此,代碼訪問安全性檢查無法關閉。當環境完全可信時,安全性會檢查快捷方式,並且此示例應用程序未啓用部分信任,所以它沒有太大的區別。在將C++/CLI程序集加載到更大的.NET應用程序的實際場景中,安全檢查的影響可能會很大,因爲每個程序都需要堆棧散步。緩解這種情況的一種方法是,儘可能在調用堆棧中儘可能靠近CAS斷言,任何時候都會按順序執行許多p/invoke調用,但以部分信任方式運行會減慢對本機代碼的調用。

// compile with /clr:safe 
[DllImport("user32.dll", CharSet=CharSet::Auto)] 
int MessageBox(System::IntPtr, System::String^ text, System::String^ caption, unsigned type); 

int main(void) 
{ 
    MessageBox(System::IntPtr::Zero, "Hi all", "Win32 Message Box", 0); 
} 

這是問題的示例#2。這是悲慘的比較慢,但仍可能不足以注意到MessageBox本身的成本旁邊。原因是用戶提供的p/invoke簽名與編譯器可以使用頭文件自動生成的簽名相比是非常不理想的。支持缺少GetLastError,但參數類型從SByte*更改爲System::String^。現在p/invoke有相當多的工作要做:分配垃圾收集器無法移動的空間並將字符串內容複製到那裏。更糟糕的是,CharSet.Auto意味着ANSI。 System::String是Unicode。因此,P/Invoke不僅需要複製字符串內容,還需要執行Unicode - > ANSI轉換。

同樣,對於MessageBox,開銷與任務相比是微不足道的。但對於其他功能,如glVertex2iGetClassName,複製參數,Unicode < - > ANSI轉換和CAS堆棧行程等額外工作可能會導致性能下降或中斷。

+0

太棒了!感謝您提供的所有信息,這是一個理想的答案,再次感謝您。 – Aan 2011-05-17 21:16:44

1

通過分析只是測試它。調用100k次的方法,計時並打印出每個測試的數字。

+0

...並在此頁上寫結果以幫助其他人:) – csl 2011-05-16 07:21:09

相關問題