2015-11-18 60 views
6

我有下面的C#代碼在釋放模式試圖基準:在64位下執行速度很慢。可能的RyuJIT錯誤?

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication54 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     int counter = 0; 
     var sw = new Stopwatch(); 
     unchecked 
     { 
      int sum = 0; 
      while (true) 
      { 
       try 
       { 
        if (counter > 20) 
         throw new Exception("exception"); 
       } 
       catch 
       { 
       } 

       sw.Restart(); 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed); 
      } 

     } 
    } 
} 
} 

我是一個64位的機器上,並且VS 2015年安裝。當我在32位下運行代碼時,它運行在0.6秒左右的每次迭代,並打印到控制檯。當我在64位下運行它時,每次迭代的持續時間只需跳到4秒!我在我的同事電腦上只安裝了VS 2013的示例代碼。有32位和64位版本運行在0.6秒。除此之外,如果我們只是刪除try catch塊,它也會在的0.6秒內運行,其中的VS 2015以64位的形式運行,時間爲

這看起來像是一個嚴重的RyuJIT迴歸,當有一個try catch塊時。我對麼 ?

+0

你的電腦是超級天才!對我來說,每次迭代需要大約10秒:(這裏沒有什麼區別,32位和64位都給出了相同的結果) –

+0

@ M.kazem。我認爲這是不可能的。我的電腦是Surface Pro 3 i7,帶有U你可以確定你在release模式下運行它,並且在不調試的情況下啓動嗎?順便說一下,我嘗試了4種不同的計算機。 –

+0

Oh。no no。I Got it。因爲你啓用了選項'優化代碼'在解決方案的屬性,現在我得到'1.3s'爲32bit和'3.9s'爲64位 –

回答

11

基準是一種美術。對您的代碼進行小修改:

Console.WriteLine("{0}", sw.Elapsed, sum); 

而現在您將看到差異消失。換句話說,x86版本現在和x64代碼一樣慢。你也許可以找出RyuJIT沒有做傳統抖動做了什麼,從這個微小的變化,但不排除不必要的

sum += i; 

有些事情,你可以看到當你看生成的機器代碼調試> Windows>反彙編。這在RyuJIT中確實是一個怪癖。它的死碼消除不如傳統抖動那麼徹底。否則,並非完全沒有理由,微軟重寫了x64抖動,因爲它不能輕易修復的錯誤。其中之一是優化器相當討厭的問題,它在優化方法上花費的時間沒有上限。對於擁有非常大的物體的方法造成相當差的行爲,它可能會在樹林中長達幾十毫秒,並導致明顯的執行暫停。

稱之爲錯誤,不是。寫出健全的代碼和抖動不會讓你失望。優化確實永遠在平常的地方,在程序員的耳朵之間開始。

0

經過一些測試後,我得到了一些有趣的結果。我的測試圍繞着try catch區塊展開。正如OP所指出的那樣,如果你刪除這個塊,執行的時間是一樣的。我已經進一步縮小了這一點,並得出結論,這是因爲counter變量在try塊中的if語句中。

允許刪除冗餘throw

   try 
       { 
        if (counter== 0) { } 
       } 
       catch 
       { 
       } 

你會得到相同的結果與此代碼,你與原來的代碼一樣。

允許變化計數器是一個實際的int值:

   try 
       { 
        if (1 == 0) { } 
       } 
       catch 
       { 
       } 

利用該代碼,64位版本執行時間已下降爲4秒至約1.7秒。仍然是32位版本的兩倍。不過,我認爲這很有趣。不幸的是,在我快速的Google搜索之後,我還沒有提出一個理由,但是如果我找出原因,我會多挖一點並更新這個答案。

至於剩下的第二個我們想削減64位版本,我可以看到,這是在for循環中遞增sumi。 允許改變此設置sum不超過其界限:

  for (int i = 0; i < int.MaxValue; i++) 
      { 
       sum ++; 
      } 

這種變化(具有在try塊的變化一起)64位應用程序的執行時間將減少到0.7秒。我對時間差異1秒的推理是由於64位版本需要處理自然爲32位的int的人爲方式。

在32位版本中,有32位分配給Int32(sum)。當sum超出其界限時,很容易確定這一事實。

在64位版本中,有64位分配給Int32(sum)。當總和超過其界限時,需要有一種機制來檢測這種情況,這可能導致減速。由於分配的冗餘位增加,甚至增加sum & i的操作也會花費更長的時間。

我在這裏理論;所以不要把這當作福音。我只是想我會發表我的發現。我相信別人能夠揭示我發現的問題。

-

更新

@HansPassant的回答指出,因爲它被認爲是不必要的sum += i;線可被消除,這非常有意義,sum不被使用的for循環之外。在他介紹for循環之外的sum值之後,我們注意到x86版本和x64版本一樣慢。所以我決定做一些測試。讓我們改變for循環和打印到以下幾點:

   int x = 0; 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
        x = sum; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed + " " + x); 

你可以看到,我介紹了一個新的int x正在被分配在for循環sum值。 x的值不會寫入控制檯。 sum不會離開for循環。這個信不信由你,實際上將x64的執行時間縮短到了0.7秒。但是,x86版本跳到1.4秒。

+0

如果'sum'高於其界限並不重要 - 它在未經檢查的上下文中運行,所以如果代碼完全運行,則忽略溢出。正如Hans所指出的那樣,由於'sum'變量在這個循環之後沒有被讀取,它可能*(x86,舊的x64 JIT)完全消除循環作爲優化。 –

+0

@Damien_The_Unbeliever你實際上提出了一個非常有趣的觀點。讓我用我的發現更新我的答案。 –

+0

@Damien_The_Unbeliever我不認爲這是未經檢查的事實實際上很重要。這只是意味着當sum達到限制時肯定不會拋出OverflowException。 (不像如果你改變它檢查)。無論如何,它仍然需要對64位的邊界進行仿真檢查,因爲int有64位分配給它。理論上我猜。再次,我不是這些問題的專家,這只是對我感興趣。 –