2011-01-03 13 views
17

Ich bin der Überzeugung gewesen, dass Casting unter bestimmten Umständen zu messbaren Leistungseinbußen führen kann. Dies kann auch der Fall sein, wenn wir anfangen, mit inkohärenten Netzen von unangenehmen Ausnahmen umzugehen.Interface Casting vs. Class Casting

Angesichts der Tatsache, dass ich mehr korrekte Heuristiken erstellen möchte, wenn es um die Programmierung geht, wurde ich aufgefordert, diese Frage den .NET-Gurus da draußen zu stellen: Ist Interface-Casting schneller als Classcasting?

Um einen Code Beispiel zu geben, sagen wir, das gibt es:

public interface IEntity { IParent DaddyMommy { get; } } 
public interface IParent : IEntity { } 
public class Parent : Entity, IParent { } 
public class Entity : IEntity 
{ 
    public IParent DaddyMommy { get; protected set; } 
    public IParent AdamEve_Interfaces 
    { 
     get 
     { 
      IEntity e = this; 
      while (e.DaddyMommy != null) 
       e = e.DaddyMommy as IEntity; 
      return e as IParent; 
     } 
    } 
    public Parent AdamEve_Classes 
    { 
     get 
     { 
      Entity e = this; 
      while (e.DaddyMommy != null) 
       e = e.DaddyMommy as Entity; 
      return e as Parent; 
     } 
    } 
} 

So ist AdamEve_Interfaces schneller als AdamEve_Classes? Wenn ja, um wie viel? Und wenn Sie die Antwort wissen, warum?

+37

In der Tat ist die moderne OOP Version von Spaghetti Code ** Lasagne Code **: viele Schichten ... :-) – rsenna

+0

@rsenna ~ Hahaha! Weißt du, ich stehe korrigiert: P – Squirrelsama

+0

@rsenna: Das ist genial. – NotMe

Antwort

7

Schauen Sie sich hier:

http://thatstoday.com/robbanp/blog/6/25/csharp-performance--cast-vs-interface

Und, ja, Sie scheinen richtig zu sein.

Bearbeiten Nun, es scheint, dass ich falsch lag. Und wie mein "patrício" Martinho Fernandes kommentiert, ist der obige Link völlig falsch (aber ich behalte ihn hier, um ehrlich zu schneiden).

ich etwas Zeit heute haben, also habe ich eine einfache Leistungsmess Code geschrieben:

public partial class Form1 : Form 
{ 
    private const int Cycles = 10000000; 

    public interface IMyInterface 
    { 
     int SameProperty { get; set; } 
    } 

    public class InterfacedClass : IMyInterface 
    { 
     public int SameProperty { get; set; } 
    } 

    public class SimpleClass 
    { 
     public int SameProperty { get; set; } 
    } 

    public struct InterfacedStruct : IMyInterface 
    { 
     public int SameProperty { get; set; } 
    } 

    public struct SimpleStruct 
    { 
     public int SameProperty { get; set; } 
    } 

    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void Form1_Load(object sender, EventArgs e) { 
     var simpleClassTime = MeasureSimpleClass(); 
     var interfacedClassTime = MeasureInterfacedClass(); 
     var simpleStructTime = MeasureSimpleStruct(); 
     var interfacedStructTime = MeasureInterfacedStruct(); 

     var message = string.Format(
      "simpleClassTime = {0}\r\ninterfacedClassTime = {1}\r\nsimpleStructTime = {2}\r\ninterfacedStructTime = {3}", 
      simpleClassTime, 
      interfacedClassTime, 
      simpleStructTime, 
      interfacedStructTime 
     ); 

     textBox.Text = message; 
    } 

    private static long MeasureSimpleClass() { 
     var watch = Stopwatch.StartNew(); 
     var obj = new SimpleClass(); 

     for (var i = 0; i < Cycles; i++) 
     { 
      obj.SameProperty = i; 
      var j = obj.SameProperty; 
      obj.SameProperty = j; 
     } 

     return watch.ElapsedMilliseconds; 
    } 

    private static long MeasureInterfacedClass() { 
     var watch = Stopwatch.StartNew(); 
     IMyInterface obj = new InterfacedClass(); 

     for (var i = 0; i < Cycles; i++) { 
      obj.SameProperty = i; 
      var j = obj.SameProperty; 
      obj.SameProperty = j; 
     } 

     return watch.ElapsedMilliseconds; 
    } 

    private static long MeasureSimpleStruct() 
    { 
     var watch = Stopwatch.StartNew(); 
     var obj = new SimpleStruct(); 

     for (var i = 0; i < Cycles; i++) 
     { 
      obj.SameProperty = i; 
      var j = obj.SameProperty; 
      obj.SameProperty = j; 
     } 

     return watch.ElapsedMilliseconds; 
    } 

    private static long MeasureInterfacedStruct() 
    { 
     var watch = Stopwatch.StartNew(); 
     IMyInterface obj = new InterfacedStruct(); 

     for (var i = 0; i < Cycles; i++) 
     { 
      obj.SameProperty = i; 
      var j = obj.SameProperty; 
      obj.SameProperty = j; 
     } 

     return watch.ElapsedMilliseconds; 
    } 
} 

Und das Ergebnis ist:

simpleClassTime = 274 
interfacedClassTime = 339 
simpleStructTime = 247 
interfacedStructTime = 302 

ich wirklich, dass eine Schnittstelle zu denken verwendet wäre schneller für class Typen und langsamer für struct (seit Boxen/Unboxing ist in letzterem beteiligt), aber das ist nicht der Fall: eine konkrete Klasse/Struktur-Referenz ist immer schneller, so scheint es.

Auch, wen es betrifft: Ich glaube, dass Leistung nicht ein gutes Kriterium für die Entscheidung, ob eine Schnittstelle sollte oder nicht verwendet werden sollte. Der Unterschied ist, wie andere hier gesagt, vernachlässigbar.

+1

@Martinho Fernandes: wow, ein einfaches "Du liegst falsch" würde ausreichen. Auch hier ist es "wie ein totes Pferd zu schlagen". :-) Ich werde versuchen, einen relevanteren Link zu finden, bleib dran. – rsenna

+6

Ich finde "Sie sind falsch" ohne eine Erklärung, um unhöflich zu sein. Das ist wie ein Downvote ohne Erklärung. –

+2

@Martinho Fernandes: Nun, du warst doch unhöflich; Du hast nur ein bisschen länger gebraucht, um dorthin zu kommen. Aber, bitte, schau dir meine bearbeitete Antwort an. Du hattest recht, ich habe mich geirrt, also * mea maxima culpa *. – rsenna

7

Sie müssten messen.

Aber wenn Casting zu einem (potenziellen) Engpass in Ihrem Code wird, sind Sie weit über Spaghetti im Problemmenü hinaus.

+0

Ich stimme voll und ganz zu. Aber ich habe dieses Ding, wo, angesichts der verfügbaren Zeit, die Verbesserung meines Codes ein Muss ist. Nennen Sie es intellektuelle Masturbation, wenn Sie müssen. : P – Squirrelsama

2

Zuerst müssen Sie hier nicht gießen, da der Code ohne Casting funktionieren muss. IParent ist ein IEntity so sollte es einfach funktionieren.

Beeinflusst das Gießen die Leistung? Geringfügig, wenn es umwandeln geht (wenn der Typ IConvertible implementiert und die Konvertierung erforderlich ist). Ansonsten ist es vernachlässigbar, da alles, was es zu tun hat, ist eine Typ Überprüfung zu tun, die blitzschnell sein sollte.

+0

Während ich die Korrektur zu schätzen weiß, war der Zweck, den Unterschied zu demonstrieren, über den ich eigentlich gesprochen habe.:) – Squirrelsama

+0

Ob eine Umwandlung implizit ist oder nicht, beseitigt nicht die Tatsache, dass eine Umwandlung stattfindet. Umwandlungen in Schnittstellen erfordern, dass der Compiler die am weitesten abgeleitete Implementierung findet, da eine Basisklasse eine Schnittstelle (sogar nicht virtuell) implementieren kann und dann eine abgeleitete Klasse die Schnittstelle erneut implementieren kann. –

4

Angenommen, es sind keine statischen Umwandlungsoperatoren definiert, sollte die Besetzung ungefähr die gleiche Zeit dauern.Es gibt möglicherweise einige "Inlining" -Optimierungen, die möglich sind, wenn eine Methode für eine Klasse und nicht für eine Schnittstelle aufgerufen wird, aber das wird nicht bemerkt, es sei denn, sie ruft eine Methode insane.

Über alles; Es gibt kein signifikantes Leistungsproblem bei beiden. Oder um es anders auszudrücken: bis ich profiliert habe und gezeigt das ist wichtig, würde ich zuerst woanders suchen.

4

Haben Sie versucht, es zu testen? Hier ist eine Schleife, die 10.000.000 Mal läuft. Auf meiner Maschine dauert die Schnittstellenversion ca. 440 ms und die Klassenversion ca. 410 ms. So ziemlich nah, aber insgesamt gewinnt die Klassenversion.

using System; 

namespace ConsoleApplication1 
{ 
    public interface IEntity { IParent DaddyMommy { get; } } 
    public interface IParent : IEntity { } 
    public class Parent : Entity, IParent { } 
    public class Entity : IEntity 
    { 
     public IParent DaddyMommy { get; protected set; } 
     public IParent AdamEve_Interfaces 
     { 
      get 
      { 
       IEntity e = this; 
       while (this.DaddyMommy != null) 
        e = e.DaddyMommy as IEntity; 
       return e as IParent; 
      } 
     } 
     public Parent AdamEve_Classes 
     { 
      get 
      { 
       Entity e = this; 
       while (this.DaddyMommy != null) 
        e = e.DaddyMommy as Entity; 
       return e as Parent; 
      } 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Entity X = new Entity(); 
      Parent P; 
      IParent IP; 
      System.Diagnostics.Stopwatch ST = new System.Diagnostics.Stopwatch(); 
      Int32 i; 

      ST.Start(); 
      for (i = 0; i < 10000000; i++) 
      { 
       IP = X.AdamEve_Interfaces; 
      } 
      ST.Stop(); 
      System.Diagnostics.Trace.WriteLine(ST.ElapsedMilliseconds); 

      ST.Reset(); 

      ST.Start(); 
      for (i = 0; i < 10000000; i++) 
      { 
       P = X.AdamEve_Classes; 
      } 
      ST.Stop(); 
      System.Diagnostics.Trace.WriteLine(ST.ElapsedMilliseconds); 
     } 
    } 

} 
+2

du meinst wahrscheinlich die ** Klasse ** Version gewinnt (es ist schneller). – rsenna

+0

@rsenna, danke, du hast Recht! Ich floppte den Code herum, nachdem ich ihn geschrieben hatte, um sicherzustellen, dass die Ergebnisse nicht von der Lade-Reihenfolge oder etwas abhängig waren. –

16

Eine Reihe der hier Antworten deuten darauf hin, Benchmarking, das ein Schritt in die richtige Richtung, aber nur der erste Schritt auf der Reise.

Mein Team hat in diesem Bereich viel Profiling und Benchmarking durchgeführt. Die kurze Version ist ja, gibt es Situationen, in denen Schnittstellen eine kleine, aber messbare Kosten verursachen. Die tatsächlichen Kosten hängen jedoch von einer Vielzahl von Faktoren ab, einschließlich der Anzahl der unterstützten Schnittstellen, der Anzahl der Schnittstellen, auf die eine Referenz angewendet wird, des Zugriffsmusters und so weiter. Die CLR verfügt über sehr viele Heuristiken, um den Schnittstellenzugriff in häufigen Fällen zu beschleunigen.

Wenn Sie einen dieser häufigen Fälle vergleichen, aber Ihr tatsächliches Programm in einen weniger häufigen Fall fällt, dann ist Ihr Benchmarking aktiv schädlich, weil es Ihnen Daten gibt, die irreführend sind.

Weit besser zu realistischen Leistungsmessungen auf echte Code. Verwenden Sie einen Profiler, schreiben Sie den Code in beide Richtungen und sehen Sie, ob der Weg messbar und wiederholbar schneller ist, auf eine für den Benutzer sichtbare und relevante Weise.

Für Ihren Hinweis auf Werfen und Fangen: die Kosten für das Werfen und Fangen sollten irrelevant sein. Ausnahmen sind per Definition Ausnahme, nicht gemeinsamen. Außerdem deuten Ausnahmen normalerweise darauf hin, dass etwas in Kürze anhalten wird; Es ist normalerweise egal, ob etwas so schnell wie möglich anhält. Wenn Sie in einer Situation sind, in der Ihre Leistung durch Ausnahmen gesteuert wird, haben Sie größere Probleme zu lösen: aufhören, so viele Ausnahmen zu werfen. Eine ausgelöste Ausnahme sollte extrem selten sein.

+1

Schöne Antwort. Konnte zu Ausnahmen nicht mehr zustimmen. – Squirrelsama

+0

Ich war überrascht zu sehen, eine Situation mit der XBox mit XNA C#, wo einige Klassen eine scheinbar vernünftige Anzahl von Schnittstellen implementiert und es gab eine erhebliche Strafe für das Casting zu ihnen. Ich werde nach mehr Informationen darüber suchen, warum es viel mehr war, als ich erwartet hatte, aber ich wollte nur eine Notiz hier ablegen, um zu sagen, dass es manchmal mehr als ein kleines Leistungsproblem ist. Für mich hat das Entfernen einiger Reocurring-Casts mit einem einmaligen Cast & Cache mir einige Millisekunden erspart, was sehr viel ist, wenn man bedenkt, dass nur 16 von ihnen verfügbar sind, um ein 60Hz-Display zu erhalten. –