2014-03-07 140 views
15

我正在優化我的代碼,並且我注意到使用屬性(甚至是自動屬性)對執行時間有着深遠的影響。看下面的例子:使用屬性和性能

[Test] 
public void GetterVsField() 
{ 
    PropertyTest propertyTest = new PropertyTest(); 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    propertyTest.LoopUsingCopy(); 
    Console.WriteLine("Using copy: " + stopwatch.ElapsedMilliseconds/1000.0); 

    stopwatch.Restart(); 
    propertyTest.LoopUsingGetter(); 
    Console.WriteLine("Using getter: " + stopwatch.ElapsedMilliseconds/1000.0); 
    stopwatch.Restart(); 
    propertyTest.LoopUsingField(); 
    Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds/1000.0); 
} 

public class PropertyTest 
{ 
    public PropertyTest() 
    { 
     NumRepet = 100000000; 
     _numRepet = NumRepet; 
    } 

    int NumRepet { get; set; } 
    private int _numRepet; 
    public int LoopUsingGetter() 
    { 
     int dummy = 314; 
     for (int i = 0; i < NumRepet; i++) 
     { 
      dummy++; 
     } 
     return dummy; 
    } 

    public int LoopUsingCopy() 
    { 
     int numRepetCopy = NumRepet; 
     int dummy = 314; 
     for (int i = 0; i < numRepetCopy; i++) 
     { 
      dummy++; 
     } 
     return dummy; 
    } 

    public int LoopUsingField() 
    { 
     int dummy = 314; 
     for (int i = 0; i < _numRepet; i++) 
     { 
      dummy++; 
     } 
     return dummy; 
    } 
} 

Release模式我的機器上我得到:

Using copy: 0.029 
Using getter: 0.054 
Using field: 0.026 

這在我的情況是一場災難 - 最關鍵的循環,如果我想只是不能使用任何屬性以獲得最大的性能。

我在做什麼錯在這裏?我在想這些是JIT optimizerinlined

+9

當你說「在發佈模式下」你是指release * build *配置,還是在沒有調試器的情況下運行?如果你在調試器中運行,我完全希望看到一個重大的打擊。還要注意,一個循環很不尋常,因爲這個循環很緊張......並且合理地微調優化*僅僅是那些被證明是瓶頸的應用程序部分。 –

+8

我剛剛測試了自己的代碼,x86 JIT使屬性訪問與字段訪問基本相同。 x64 JIT顯示了你在問題中的行爲。您可能想嘗試即將推出的新x64 JIT:http://blogs.msdn.com/b/dotnet/archive/2014/02/27/ryujit-ctp2-getting-ready-for-prime- time.aspx –

+0

@JonSkeet,我的意思是發佈版本配置。我從ReSharper測試跑步者那裏運行這些測試是精確的。 – Grzenio

回答

-1

您必須檢查優化代碼複選框是否被選中。

  1. 如果未選中,進入屬性仍然方法調用
  2. 如果檢查屬性在內襯和性能是一樣的,可直接現場訪問,因爲JIT編譯的代碼會相同

對X64 JIT編譯器中的inlinig有更多限制。有關JIT64內聯優化的更多信息,請參閱:http://blogs.msdn.com/b/davbr/archive/2007/06/20/tail-call-jit-conditions.aspx

請參閱點#3 The caller or callee return a value type。 如果你的財產將返回引用類型,屬性getter將被內聯。 這意味着int NumRepet { get; set; }沒有內聯,但object NumRepet { get; set; }將內聯,如果你不打破另一個限制。

X64 JIT的優化是很差,這就是爲什麼新的將被引入約翰提

+1

不正確。默認情況下,發佈版本會檢查「優化代碼」,即使使用優化的代碼,也會出現性能問題。 – ken2k

+0

我編輯了我關於X64的帖子JIT – Aik

+0

「如果檢查到屬性是內聯的並且性能相同」仍然不正確,則可以使用當前的x64 JIT編譯器對其進行測試。 – ken2k

0

你說你要優化你的代碼,但我很好奇,怎麼樣,有什麼功能應該是,以及源數據是什麼以及它的大小,因爲這顯然不是「真實」的代碼。如果您在分析大量數據時考慮使用BinarySearch功能。這比使用非常大的數據集的.Contains()函數要快得多。

List<int> myList = GetOrderedList(); 
if (myList.BinarySearch(someValue) < 0) 
// List does not contain data 

也許你只是循環瀏覽數據。如果您正在循環數據並返回一個值,那麼您可能需要使用yield關鍵字。另外,如果可以的話,考慮潛在的並行庫的使用,或者利用您自己的線程管理。

這看起來不像你想要的來源,但它是非常通用的,所以我想這是值得一提的。

public IEnumerable<int> LoopUsingGetter() 
{ 
    int dummy = 314; 

    for (int i = 0; i < NumRepet; i++) 
    { 
     dummy++; 
     yield return dummy; 
    } 
} 

[ThreadStatic] 
private static int dummy = 314; 

public static int Dummy 
{ 
    get 
    { 
     if (dummy != 314) // or whatever your condition 
     { 
      return dummy; 
     } 

     Parallel.ForEach (LoopUsingGetter(), (i) 
     { 
      //DoWork(), not ideal for given example, but due to the generic context this may help 
      dummy += i; 
     }); 
    } 

    return dummy; 
} 
+0

在我們的案例中,並行化是在更高層次上完成的,所以我優化的代碼應該保持單線程。儘管謝謝你的一個有效的觀點! – Grzenio

1

getter/setter方法是用一些特殊約定方法語法糖(在二傳手「值」變量「並沒有明顯的參數列表)。

根據這一article,」如果有任何的該方法的正式參數是結構體,方法不會被內聯。「 - 整數是結構體,因此我認爲這個限制是適用的。

我沒有看過由以下代碼製作的IL,但我沒有得到,我認爲這顯示了工作這樣一些有趣的結果...

using System; 
using System.Diagnostics; 

public static class Program{ 
public static void Main() 
{ 
    PropertyTest propertyTest = new PropertyTest(); 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    propertyTest.LoopUsingField(); 
    Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds/1000.0); 


    stopwatch.Restart(); 
    propertyTest.LoopUsingBoxedGetter(); 
    Console.WriteLine("Using boxed getter: " + stopwatch.ElapsedMilliseconds/1000.0); 

    stopwatch.Restart(); 
    propertyTest.LoopUsingUnboxedGetter(); 
    Console.WriteLine("Using unboxed getter: " + stopwatch.ElapsedMilliseconds/1000.0); 

} 

} 
public class PropertyTest 
{ 
    public PropertyTest() 
    { 
     _numRepeat = 1000000000L; 
     _field = 1; 
     Property = 1; 
     IntProperty = 1; 
    } 

    private long _numRepeat; 
    private object _field = null; 
    private object Property {get;set;} 
    private int IntProperty {get;set;} 

    public void LoopUsingBoxedGetter() 
    { 

     for (long i = 0; i < _numRepeat; i++) 
     { 
      var f = Property; 
     } 

    } 

    public void LoopUsingUnboxedGetter() 
    { 
     for (long i = 0; i < _numRepeat; i++) 
     { 
      var f = IntProperty; 
     } 
    } 

    public void LoopUsingField() 
    { 
     for (long i = 0; i < _numRepeat; i++) 
     { 
      var f = _field; 
     } 
    } 
} 

這將產生..在我的機器,OS X(新版本的單聲道),這些結果(以秒爲單位):

  • 使用字段:2.606
  • 使用盒裝的getter:2.585
  • 使用未裝箱的getter:2.71
+0

在Windows(.net v4.5,x64)上,我沒有得到有意義的,或重複性的時間差異。由於JIT開銷(尤其是對於持續時間僅爲50毫秒的「小」運行),更改基準的順序也會影響結果。 –

-1

,當它完成了循環,當你正在寫來安慰這個可以添加額外的時間會扭曲你的結果你的秒錶仍在運行,應停止秒錶。

[Test] 
public void GetterVsField() 
{ 
    PropertyTest propertyTest = new PropertyTest(); 
    Stopwatch stopwatch = new Stopwatch(); 

    stopwatch.Start(); 
    propertyTest.LoopUsingCopy(); 
    stopwatch.Stop(); 
    Console.WriteLine("Using copy: " + stopwatch.ElapsedMilliseconds/1000.0); 

    stopwatch.Reset(); 
    stopwatch.Start(); 
    propertyTest.LoopUsingGetter(); 
    stopwatch.Stop(); 
    Console.WriteLine("Using getter: " + stopwatch.ElapsedMilliseconds/1000.0); 

    stopwatch.Reset(); 
    stopwatch.Start(); 
    propertyTest.LoopUsingField(); 
    stopwatch.Stop(); 
    Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds/1000.0); 
} 
+0

小心解釋反對票嗎? – Marko

+0

在方法運行之前評估該方法的參數。評估該屬性的價值是在該行代碼上執行的第一件事情之一。停止秒錶甚至可能需要更多時間。最重要的是,兩種解決方案之間的差異將實際爲零,即使速度較慢,也會如此。 – Servy

1

按照80/20的性能規則,而微優化。 爲可維護性編寫代碼,而不是性能。 也許彙編語言是最快的,但這並不意味着我們應該爲所有目的使用匯編語言。

您正在運行循環1億次,差值爲0.02毫秒或20微秒。調用一個函數會產生一些開銷,但在大多數情況下並不重要。你可以信任編譯器來內聯或者做高級的事情。

99%的情況下,直接訪問該字段會產生問題,因爲當您發現某些內容出錯時,您無法控制所有變量的引用位置,並在太多的位置進行修復。