2012-07-13 98 views
7

我注意到啓動時間會因此而有所不同,因此我放置了一段初始化代碼。我覺得這很奇怪,所以我寫了一個小基準,證實了我的懷疑。看來在調用main方法之前執行的代碼比正常情況慢。靜態構造函數中的代碼運行速度較慢

爲什麼Benchmark();以不同的速度運行,取決於在常規代碼路徑之前和之後調用?

這裏的基準代碼:

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

結果表明:Benchmark()運行幾乎兩倍兩個靜態構造函數和構造函數static Program program財產慢:

// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

倍增迭代的次數基準循環會使所有時間翻倍,這表明所發生的性能損失不是恆定的,而是一個因素。

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

爲什麼會這樣呢?這將是有意義的,如果初始化如果它完成它所屬的地方一樣快。測試是在.NET 4,發佈模式,優化。

+2

究竟是什麼問題? – jcolebrand 2012-07-13 03:17:08

+0

編譯設置?框架版本? – user7116 2012-07-13 03:24:41

+0

請看看我的編輯是否讓你感覺(我不知道爲什麼),試着在.Net 4/release上得到類似的結果。 – 2012-07-13 03:52:17

回答

3

這是一個非常有趣的問題。我花了一些時間試驗你的程序的變體。這裏有幾點看法:

  1. 如果將基準()靜態方法分成不同的類,對於靜態構造函數的性能損失消失。

  2. 如果您將Benchmark()方法變爲實例方法,則性能損失將消失。當我分析你的快速病例(1,2)和慢速病例(3,4)時,慢速病例花費了額外的時間在CLR幫助程序方法中,特別是JIT_GetSharedNonGCStaticBase_Helper。

基於這些信息,我可以推測發生了什麼。 CLR需要確保每個靜態構造函數至多執行一次。一個複雜的情況是靜態構造函數可能會形成一個循環(例如,如果A類包含B類型的靜態字段而B類包含A類型的靜態字段)。

在靜態構造函數內部執行時,JIT編譯器會插入一些靜態方法調用的檢查,以防止由於循環類依賴關係而導致潛在的無限循環。一旦從靜態構造函數外部調用靜態方法,CLR將重新編譯該方法以除去檢查。

這應該是非常接近正在發生的事情。

+0

感謝您更多地關注此問題,尤其是分析。雖然我不確定你的分析。這意味着我的測試會有不斷的開銷。我已更新我的問題,以表明性能損失似乎是一個因素。 – Zong 2012-07-15 15:02:42

+0

每個IsPrime()調用**都有一個額外的開銷**,因爲每個IsPrime調用都必須被檢查包圍。例如,如果您使IsPrime更昂貴(例如,調用IsPrime(1000000000 + 2 * i)),則四種情況之間的差異消失。 – 2012-07-15 18:21:22

+0

即,如果將迭代次數加倍,則還會使IsPrime調用次數加倍,因此會使檢查次數增加一倍。 – 2012-07-15 18:23:30

3

這是一個非常有據可查的事實。

靜態構造函數很慢。 .net運行時不夠智能來優化它們。

參考:Performance penalty of static constructors

明確的靜態構造函數是昂貴的,因爲他們 要求運行,以確保該值設置完全相同 被訪問類的任何成員之前。確切的成本是 取決於情況,但在某些情況下它可能非常明顯。

+0

我不認爲鏈接的文章適用。文章說,帶有靜態構造函數的類型在beforeFieldInit標記,因此使用該類型會導致額外的運行時成本。但是,OP沒有比較兩種類型,一種是靜態構造函數,另一種是沒有。整個基準測試只有一種類型,並且該類型具有靜態構造函數。所以,其他事情正在發生。 – 2012-07-13 06:40:16

+0

你是對的;我的基準實際上表明文章中兩個案例的表現或多或少是相同的。 – Zong 2012-07-15 14:56:09