2016-05-31 6 views
3

Ich führte einen Test durch, bei dem die Geschwindigkeitsdifferenz zwischen den beiden folgenden Accessor-Funktionen gemessen wurde, und der Zeitunterschied war größer als ich erwartet hatte. Ich hatte einfach das Gefühl, dass die Kurzschreibweise etwas schneller sein könnte, also wollte ich es testen.Warum funktionieren Kurzschreibzugriffe schneller als ihre regulären Gegenstücke?

Ich habe die gesamten benötigten Sekunden gemessen, um die Get-Funktion für jede Klasse aufzurufen, für 1 Milliarde Iterationen.

using System; 
using System.Diagnostics; 

class SimpleGet { 
    int value; 

    public int Get() { 
     return value; 
    } 
} 

class ShorthandGet { 
    int value; 

    public int Get() => value; 
} 

class Program { 
    static void Main() { 
     const int Iterations = 1000000000; 
     Stopwatch sw = new Stopwatch(); 

     sw.Start(); 
     { 
      int n; SimpleGet sg = new SimpleGet(); 
      for (int i = 0; i < Iterations; i++) { 
       n = sg.Get(); 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine("SimpleGet: " + sw.Elapsed.TotalSeconds); 

     sw.Reset(); 

     sw.Start(); 
     { 
      int n; ShorthandGet shg = new ShorthandGet(); 
      for (int i = 0; i < Iterations; i++) { 
       n = shg.Get(); 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine("ShorthandGet: " + sw.Elapsed.TotalSeconds); 

     Console.ReadLine(); 

    } 
} 

Die Ergebnisse:

// 1 billion iterations 
SimpleGet: 11.8484244 
ShorthandGet: 4.3218568 

Der Geschwindigkeitsunterschied sehr groß ist. Der einzige Unterschied, den ich sehen konnte, ist, dass die reguläre Funktion Klammern hat und daher beim Funktionsaufruf einen neuen Bereich erstellt. Da es keine neuen Variablen innerhalb des Scopes gibt, sollte theoretisch nicht "außer Acht gelassen" werden. Kann jemand erklären, warum die reguläre Funktion nicht auf dem gleichen Niveau wie das andere optimiert wird?

bearbeiten

ich das gleiche Szenario mit Eigenschaften getestet: Value { get { return value; } } und Value => value; und die Zeitdifferenzen wir sind sehr nahe an den jeweiligen Differenzen Funktion der Zeit. Ich nehme an, die Ursache ist dieselbe.

+0

Haben Sie diesen Benchmark auf einem optimierten Build ausgeführt (d. H. Release not Debug) ohne den Debugger angeschlossen? –

+0

Ja, ich sehe die gleichen Ergebnisse wie im Debug-Modus. Wenn Sie jedoch zu Release wechseln, werden beide Schleifen auf etwa 2,3 Sekunden gesetzt. – Jonesopolis

+0

Sie haben Recht, es war Ignoranz meinerseits. Ich habe versucht, Build ohne Debugger zu erstellen, sie waren ungefähr gleich. – Dave

Antwort

8

Die kurze Antwort ist, dass es keinen Unterschied in einem ordnungsgemäß durchgeführten Benchmark gibt.

Für Mikro-Optimierungsfälle wie diese schaue ich immer zuerst auf die IL. Nicht, weil Sie einen tiefen Einblick bekommen, sondern weil, wenn ein identisches IL erzeugt wird, es zur Laufzeit keinen Unterschied geben sollte. Als Nächstes sollten Sie daran denken, dass Sie mit einem Release-Build beginnen müssen, da der Compiler unnötige IL-Anweisungen in diesen Builds entfernen wird.

In einem Debug-Build, die lange Form IL (SimpleGet) zusätzliche Anweisungen platzieren Bruchstellen zu aktivieren:

.method public hidebysig 
    instance int32 Get() cil managed 
{ 
    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldfld int32 ConsoleApplication7.SimpleGet::'value' 
    IL_0007: stloc.0 
    IL_0008: br.s IL_000a 
    IL_000a: ldloc.0 
    IL_000b: ret 
} 

gegen den viel kürzer für ShorthandGet:

.method public hidebysig 
    instance int32 Get() cil managed 
{ 
    IL_0000: ldarg.0 
    IL_0001: ldfld int32 ConsoleApplication7.ShorthandGet::'value' 
    IL_0006: ret 
} 

jedoch in einem optimierte Build, beide Formen führen zu der gleichen IL, die identisch mit ShorthandGet oben ist.

Benchmarks von Debug-Builds können Unterschiede aufweisen, wie Sie bereits gezeigt haben, aber diese sind nie einen Vergleich wert, da Sie den optimierten Code eines Release-Builds ausführen, wenn Sie Wert auf Leistung legen. Moral der Geschichte ist immer Ihre Performance-Analyse auf optimierten Code zu tun. Ein weiterer Punkt, der oft übersehen wird, ist ein Benchmark ohne den angehängten Debugger, da das JIT selbst bei optimierter IL den Debugger erkennt und mehr debugbaren Maschinencode ausgibt. Dies wird von vielen Leuten übersehen, weil sie einfach auf "Start" klicken oder F5 in VS drücken, aber dieses startet das Programm mit dem angehängten Debugger. Verwenden Sie die Menüoption Debug> Start ohne Debugging.