2012-10-30 56 views
5

我已經工作了,可以產生128000x128000像素的像一個迷宮,而一個C#迷宮發電機。所有的內存使用情況都已經過優化,所以我目前正在考慮加速這一代。循環中方法調用的開銷是多少?

的問題(以及更多關閉的興趣點),我發現了以下(只是一些示例代碼來說明問題):

這段代碼在我的機器上約1.4秒運行一次當pixelChanged爲空:

public void Go() 
{ 
    for (int i = 0; i < bitjes.Length; i++) 
    { 
     BitArray curArray = bitjes[i]; 
     for (int y = 0; y < curArray.Length; y++) 
     { 
      curArray[y] = !curArray[y]; 
      GoDrawPixel(i, y, false); 
     } 
    } 
} 

public void GoDrawPixel(int i, int y, Boolean enabled) 
{ 
    if (pixelChanged != null) 
    { 
     pixelChanged.Invoke(new PixelChangedEventArgs(i, y, enabled)); 
    } 
} 

如果下面的代碼實際上0.4秒運行速度會快

public void Go() 
{ 
    for (int i = 0; i < bitjes.Length; i++) 
    { 
     BitArray curArray = bitjes[i]; 
     for (int y = 0; y < curArray.Length; y++) 
     { 
      curArray[y] = !curArray[y]; 
      if (pixelChanged != null) 
      { 
       pixelChanged.Invoke(new PixelChangedEventArgs(i, y, false)); 
      } 
     } 
    } 
} 

看來,當打電話只是一個「空」的方法利用了約20%該算法使用的CPU。這不奇怪嗎?我試圖在調試和發佈模式下編譯解決方案,但沒有發現任何明顯的差異。

這意味着,每一個方法調用我在這個循環將約0.4秒放慢我的代碼。由於迷宮生成器代碼當前由許多獨立的方法調用組成,這些調用除了執行不同的動作外,還開始獲得大量的資源。

我也查了谷歌和其他職位堆棧溢出,但還沒有真正找到一個解決辦法。

是否可以自動優化這樣的代碼? (也許像Roslyn項目這樣的東西)或者我應該把所有東西放在一個大的方法?

編輯: 我也有興趣在這些2例JIT/CLR代碼差異或許有些分析。 (那麼,這個問題實際上來自)

EDIT2: 所有的代碼在釋放模式

回答

4

這是一個問題,JIT具有用於方法的內聯優化(其中整個方法的代碼實際上是調用父代碼內注射),但此僅發生於那些被編譯爲32個字節或更小的方法。我不知道爲什麼存在32字節的限制,並且還想在C#中像在C/C++中那樣看到一個'inline'關鍵字,完全是爲了解決這些問題。

+0

我從來沒有聽說過inline關鍵字,但它似乎正是我需要的:),我ho PE微軟也可以將它添加到C#中。 – Devedse

+0

看看這個:http://stackoverflow.com/questions/473782/inline-functions-in-c – Gilad

+0

http://www.greenend.org.uk/rjk/tech/inline.html – Gilad

3

編譯這是在調試或發佈模式?方法調用相當昂貴,但在發佈模式下構建/運行時可能會內聯。在調試模式下,它不會從編譯器獲得任何優化。

+0

我已經在釋放和調試中試過了,在調用方法的情況下,它們大約慢了0.4秒。 – Devedse

+0

@Devedse:你是怎麼內聯這些方法的? –

+0

@Luis Filipe:我手動拷貝代碼請參閱主題 – Devedse

5

我想嘗試的第一件事將使其靜態而不是實例:

public static void GoDrawPixel(PixelChangedEventHandler pixelChanged, 
    int x, int y, bool enabled) 
{ 
    if (pixelChanged != null) 
    { 
     pixelChanged.Invoke(new PixelChangedEventArgs(x, y, enabled)); 
    } 
} 

這改變了幾件事情:

  • 堆棧語義仍然可以進行比較(它加載的引用,2 ints and a bool)
  • callvirt變成call - 這樣可以避免一些小的開銷
  • ldarg0/ldfld對(this.pixelChanged)成爲單ldarg0

接下來的事情我想看看就是PixelChangedEventArgs;它可能是通過,因爲結構是便宜,如果它避免了大量的分配;或者只是:

pixelChanged(x, y, enabled); 

(原始參數,而不是一個包裝對象 - 需要簽名的變化)

+0

我已經看過pixelChanged(x,y,啓用)這確實加快了一點。 你可能會解釋一下你的意思是callvirt和通話差異嗎?和ldarg0/ldfld對? – Devedse

+0

@Devedse在實例方法中,您可以在兩處訪問'this.pixelChanged',就好像我們在參數中傳遞它,如果它只做一次;一旦引用作爲本地或參數堆棧(在本例中爲arg0),它會稍微快於**;此外,如果我們使它成爲靜態而不是實例,那麼將其作爲參數是必需的。 –

+0

我試過你的代碼,現在它的速度比0.25慢了0.25秒,而不是0.4。我們正在改進:) – Devedse

1

主要開銷是,正如馬克說,這使得虛擬呼叫並傳遞參數。在執行方法期間PixelChanged的值是否可以改變?如果不是這可能會奏效(我並不完全確定JIT將空動作委託優化爲nop,你必須自己測試它(如果不是這樣,我會忽略這裏的良好實踐,只是做調用pixelChanged.Invoke調用,一個不調用(內聯),只需調用任何最適合的套裝...畢竟有時您必須使代碼稍微不夠優雅才能使其更快)。

public void Go() 
{ 
    if (pixelChanged != null) 
    GoPixelGo((x,y,z) => { }); 
    else 
    GoPixelGo((i, y, enabled) => pixelChanged.Invoke(i, y, enabled)); 
} 

public void GoPixelGo(Action<int, int, bool> action) 
{ 
    for (int i = 0; i < bitjes.Length; i++) 
    { 
     BitArray curArray = bitjes[i]; 
     for (int y = 0; y < curArray.Length; y++) 
     { 
     curArray[y] = !curArray[y]; 
     action(i,y, false); 
     } 
    } 
} 
+0

我花了一段時間才明白,但我確實看到你現在在做什麼。我也會試試這個。 – Devedse

+0

我想這取決於您需要優化哪種情況。對於「pixelChanged爲null」的情況,由於委託調用的代價(快速,但不如空測試那麼快),我期望空測試更快。對於「pixelChanged not null」的情況,確實刪除空測試可能會有所幫助。這實際上取決於哪種情況最有可能。 –

+0

我已經試過了,它也比直接方式慢了0.25秒。 – Devedse

相關問題