2011-11-16 49 views
6

我碰到一個差排在速度使用以下兩種結構:靜態構造函數的性能和爲什麼我們不能指定beforefieldinit

public struct NoStaticCtor 
{ 
    private static int _myValue = 3; 
    public static int GetMyValue() { return _myValue; } 
} 

public struct StaticCtor 
{ 
    private static int _myValue; 
    public static int GetMyValue() { return _myValue; } 
    static StaticCtor() 
    { 
     _myValue = 3; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     long numTimes = 5000000000; // yup, 5 billion 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (long i = 0; i < numTimes; i++) 
     { 
      NoStaticCtor.GetMyValue(); 
     } 
     sw.Stop(); 
     Console.WriteLine("No static ctor: {0}", sw.Elapsed); 

     sw.Restart(); 
     for (long i = 0; i < numTimes; i++) 
     { 
      StaticCtor.GetMyValue(); 
     } 
     sw.Stop(); 
     Console.WriteLine("with static ctor: {0}", sw.Elapsed); 
    } 
} 

其產生的結果:

Release (x86), no debugger attached: 
No static ctor: 00:00:05.1111786 
with static ctor: 00:00:09.9502592 

Release (x64), no debugger attached: 
No static ctor: 00:00:03.2595979 
with static ctor: 00:00:14.5922220 

編譯器產生NoStaticCtor的靜態構造函數與StaticCtor中明確聲明的相同。我明白,當靜態構造函數沒有明確定義時,編譯器只會發出beforefieldinit

他們生產幾乎相同的IL代碼,但有一個區別,宣佈與beforefieldinit的結構,這是我覺得不同之處在於,因爲我知道它確定何時類型構造函數被調用,雖然我不是很弄清楚爲什麼會有這樣的差異。它假定它不是每次迭代都調用類型構造函數,因爲一個類型構造函數只能被調用一次。

所以,

1)爲什麼用beforefieldinit的結構和沒有一個之間的時間差? (我想JITer在for循環中做了一些額外的工作,但是,我不知道如何查看JITer的輸出以查看內容。)

2)爲什麼編譯器設計者a)沒有使所有結構體beforefieldinit是默認的,並且b)不給開發者明確指定該行爲的能力?當然,這個假設你不能,因爲我一直沒有找到辦法。


編輯:

I modified the code,第二次運行基本上每個循環,期待改進,但它並沒有太多:

No static ctor: 00:00:03.3342359 
with static ctor: 00:00:14.6139917 
No static ctor: 00:00:03.2229995 
with static ctor: 00:00:12.9524860 
Press any key to continue . . . 

我這樣做是因爲我雖然很好,也許,但不太可能是,在JITer竟是每次迭代調用類型構造函數。在我看來,JITer會知道類型構造函數已經被調用,並且不會在編譯第二個循環時發出代碼。

除了Motti的回答是: This code產生更好的效果,因爲在JIT編譯不同之處,DoSecondLoop的JIT編譯不發出靜態構造函數檢查,因爲它檢測到它是在DoFirstLoop以前那樣,使每個循環執行以相同的速度。 (〜3秒)

+2

這裏需要大量的一些觀點。您測量的開銷是* 1納秒*。是的,這是關於測試+跳轉指令的作用。 –

+0

@Hans我知道開銷很小。我從來沒有真正寫過這樣的代碼在生產中使用。我目前正在進行「CLR工作如何」的工作,我一直在討論不尋常的事情。我真的只是想知道爲什麼JITer根據編譯器發出的屬性來做出它所做的決定。我應該可以買一本書。 –

回答

10

第一次訪問類型時,必須執行靜態ctor(無論是顯式生成還是隱式生成)。

當JIT編譯器將IL編譯爲本機指令時,它會檢查該類型的靜態ctor是否已經執行,如果沒有發出檢查靜態ctor是否已被執行的本地代碼,那麼執行該代碼。

jitted代碼被緩存以便將來調用相同的方法(這就是爲什麼代碼必須再次檢查靜態ctor是否被執行)。

問題是,如果檢查靜態ctor的jitted代碼處於循環中,則此測試將在每次迭代中發生。

當beforefieldinit存在時,JIT編譯器可以通過在進入循環之前測試靜態ctor調用來優化代碼。如果不存在,則不允許此優化。

C#編譯器會自動決定何時發出此屬性。它目前(C#4)只有在代碼沒有明確定義靜態構造函數時纔會發出它。其背後的想法是,如果你自己定義了一個靜態ctor,那麼對你而言,時機可能更重要,而且strt ctor不應該提前執行。這可能會也可能不會,但是你不能改變這種行爲。

下面是在詳細講解這個我在網上.NET教程的部分鏈接:http://motti.me/c1L

BTW,我不建議使用的結構靜態構建函數,因爲無論你相信與否,他們都不能保證執行!這不是問題的一部分,所以我不會詳細說明,但如果您感興趣,請參閱以下內容以獲得更多詳細信息:http://motti.me/c1I(我在大約2點30分觸及該視頻的主題)。

我希望這有助於!

+0

不在結構上使用靜態ctors是很好的建議。我非常瞭解CLR如何不能總是執行它。現在,當我在第一次編輯中考慮到pastebin鏈接時,即使JITer知道JITer將static靜態ctor檢查放入包含StaticCtor.GetMyValue()的*兩個*循環中,而不是僅在第一個for循環中它先在代碼中放置檢查。從本質上講,在靜態ctor尚未被調用的任何函數中,它將在* EVERY *訪問之前將該檢查放在第一個,這是否正確? –

+0

嗯,通過測試,似乎正如你所說,JIT編譯器決定是否將測試放在每個連接方法的基礎上。我將每次調用的代碼修改爲兩個獨立的方法(使用&不使用ctor),並且第二次獲得了性能提升:http://pastebin.com/WyQWz5ar。我不確定這是記錄還是實現細節,在JIT編譯器的任何更新中都可能會發生變化。另外,正如我相信你知道的那樣,JIT編譯器的實現可能在不同平臺之間有所不同。 –

+0

是的,我發現它很有趣。正如我上面所評論的,我處於一個只想混淆/理解CLR的階段,並且這種行爲出現了。我對JIT如何決定何時以及如何去做着迷。謝謝! :) –

相關問題