Feld gegen Eigentum. Leistungsoptimierung

74

Bitte beachten Sie, dass diese Frage nur die Leistung betrifft. Überspringen wir Designrichtlinien, Philosophie, Kompatibilität, Portabilität und alles, was nicht mit reiner Leistung zu tun hat. Vielen Dank.

Nun zur Frage. Ich habe immer angenommen, dass das Lesen von öffentlichen Feldern schneller sein muss als das Aufrufen eines Getters, da C # -Getter / Setter wirklich verdeckte Methoden sind.

Also, um sicherzugehen, dass ich einen Test gemacht habe (der Code unten). Dieser Test führt jedoch nur dann zu erwarteten Ergebnissen (dh Felder sind mit 34% schneller als Getter ), wenn Sie ihn in Visual Studio ausführen.

Sobald Sie es über die Befehlszeile ausführen, wird fast das gleiche Timing angezeigt ...

Die einzige Erklärung könnte sein, dass die CLR zusätzliche Optimierungen vornimmt (korrigieren Sie mich, wenn ich hier falsch liege).

Ich glaube nicht, dass in einer realen Anwendung, in der diese Eigenschaften auf viel komplexere Weise verwendet werden, sie auf die gleiche Weise optimiert werden.

Bitte helfen Sie mir, die Idee zu beweisen oder zu widerlegen, dass Immobilien im wirklichen Leben langsamer sind als Felder.

Die Frage ist: Wie soll ich die Testklassen ändern, damit sich die CLR ändert, damit das öffentliche Feld die Getter übertrifft? ODER zeigen Sie mir, dass jede Eigenschaft ohne interne Logik dieselbe Leistung wie ein Feld erbringt (zumindest auf dem Getter).

EDIT: Ich spreche nur über Release x64 Build.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PropertyVsField
{
    class Program
    {
        static int LEN = 20000000;
        static void Main(string[] args)
        {
            List<A> a = new List<A>(LEN);
            List<B> b = new List<B>(LEN);

            Random r = new Random(DateTime.Now.Millisecond);

            for (int i = 0; i < LEN; i++)
            {
                double p = r.NextDouble();
                a.Add(new A() { P = p });
                b.Add(new B() { P = p });
            }

            Stopwatch sw = new Stopwatch();

            double d = 0.0;

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += a[i].P;
            }

            sw.Stop();

            Console.WriteLine("auto getter. {0}. {1}.", sw.ElapsedTicks, d);

            sw.Restart();
            for (int i = 0; i < LEN; i++)
            {
                d += b[i].P;
            }

            sw.Stop();

            Console.WriteLine("      field. {0}. {1}.", sw.ElapsedTicks, d);

            Console.ReadLine();
        }
    }

    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
}
Boppity Bop
quelle
17
Bobb, Microsoft weiß sehr gut, dass jeder, egal wie sehr er Designphilosophie / Shmilosophie trompetet, öffentliche Felder anstelle von Eigenschaften verwenden würde, sobald er feststellt, dass es 34% schneller ist (obwohl es in 99,9% der Fälle eine Mikrooptimierung ist). Leute würden es sowieso tun). Deshalb haben die Eric Lipperts der Welt einen verdammt guten Compiler und Optimierer entwickelt, der herausfindet, dass Ihre automatische Eigenschaft ein getarntes öffentliches Feld ist, und entsprechend optimiert.
Sergey Kalinichenko
5
Sie können versuchen, B zu einer Struktur anstelle einer Klasse zu machen, wenn Sie nur einen Container ohne Methoden verwenden. Sie müssen auch berücksichtigen, ob Sie Start Debugging oder Start Without Debugging ausführen, wenn Sie es in Visual Studio starten. Durch die Verwendung von Start Debugging werden viele Dinge verknüpft, damit Sie Schritte ausführen, Werte beobachten usw. können, was sich erheblich auf die Leistung auswirken kann.
JamieSee
1
@dasblinkenlight - guter Punkt ... aber es scheint, dass die Goodies von CLR kommen, nicht von C # -Compilern ... Wenn es Compiler war, wird es auch im VS so schnell sein ... vermisse ich hier etwas?
Boppity Bop
Nein. Es kann NICHT der C # -Compiler sein, da die generierte Klasse so sein muss, wie Sie es in der Struktur sagen. Sie können es ersetzen und ein using-Programm muss den neuen Eigenschaftssetter verwenden;)
TomTom
Eigenschaften haben ein zusätzliches Attribut CompilerGeneratedAttribute, wenn es in Kurzform geschrieben ist (öffentliche Zeichenfolge Test {get; set;}). Ich bin sicher, der Compiler überprüft dies beim Anpassen der IL und springt einfach zum Hintergrundfeld (wenn jemand die tatsächliche x86-Assembly überprüfen würde, dass dies tatsächlich der Fall ist, wäre das fantastisch).
drake7707

Antworten:

61

Wie andere bereits erwähnt haben, sind die Getter inline .

Wenn Sie Inlining vermeiden möchten, müssen Sie

  • Ersetzen Sie die automatischen Eigenschaften durch manuelle:

    class A 
    {
        private double p;
        public double P
        {
            get { return p; }
            set { p = value; }
        }
    } 
    
  • und sagen Sie dem Compiler, dass er den Getter nicht einbinden soll (oder beides, wenn Sie Lust dazu haben):

            [MethodImpl(MethodImplOptions.NoInlining)]
            get { return p; }
    

Beachten Sie, dass die erste Änderung keinen Unterschied in der Leistung bewirkt, während die zweite Änderung einen eindeutigen Overhead für Methodenaufrufe zeigt:

Manuelle Eigenschaften:

auto getter. 519005. 10000971,0237547.
      field. 514235. 20001942,0475098.

Kein Inlining des Getters:

auto getter. 785997. 10000476,0385552.
      field. 531552. 20000952,077111.
Heinzi
quelle
4
Sie fragen sich nur, ob Sie eine manuelle Eigenschaft verwenden müssen oder ob das Anwenden des Attributs auf den automatischen Eigenschaften-Getter weiterhin funktioniert.
Lukazoid
Gibt es einen Grund, warum man jemals Inlining verhindern möchte?
Jacob Stamm
1
Es ist alt, aber ich bin immer noch neugierig auf die Frage, die @JacobStamm gestellt hat, ob jemand darauf stößt. Ich bin mir nicht sicher, ob der Umfang klein genug ist, um eine eigene Frage zu rechtfertigen.
Sinjai
2
Was sind diese Zahlen?
Person27
@ person27: Die Ausgabe des laufenden OP-Codes. Die erste numerische Spalte ist die relevante und enthält die Anzahl der übergebenen Ticks. Kleiner ist besser (dh schneller).
Heinzi
28

Schauen Sie sich die Eigenschaften vs Felder an - Warum ist das wichtig? (Jonathan Aneja) Blog-Artikel von einem der VB-Teammitglieder auf MSDN. Er skizziert das Argument Eigenschaft gegen Felder und erklärt triviale Eigenschaften wie folgt:

Ein Argument, das ich für die Verwendung von Feldern über Eigenschaften gehört habe, ist, dass "Felder schneller sind", aber für triviale Eigenschaften, die eigentlich nicht zutreffen, da der Just-In-Time-Compiler (JIT) der CLR den Eigenschaftszugriff inline macht und Code generiert, der so ist Effizient als direkter Zugriff auf ein Feld.

JamieSee
quelle
Hallo @ Bobb, ich bezweifle nicht, dass du lesen kannst. Eigentlich gefällt mir Heinzis Antwort auch am besten. Ich dachte nur, dass dies eine nützliche kleine Referenz ist und einen guten Beitrag zum Thema im Allgemeinen leisten würde, da die Erklärung aus einer primären Quelle stammt.
JamieSee
danke Kumpel .. es war Freitag Witz keine Sorge. Ich habe Heinzi markiert, weil ich seine Antwort einige Minuten früher als Ihre gesehen habe. aber ich mag deine auch, Prost! :-)
Boppity Bop
12

Die JIT integriert jede Methode (nicht nur einen Getter), die von ihren internen Metriken bestimmt wird, schneller inline. Da es sich um eine Standardeigenschaft handelt return _Property;, wird sie in jedem Fall eingefügt.

Der Grund für das unterschiedliche Verhalten ist, dass im Debug-Modus mit angeschlossenem Debugger die JIT erheblich beeinträchtigt ist, um sicherzustellen, dass alle Stapelpositionen den Anforderungen des Codes entsprechen.

Sie vergessen auch die Leistungsregel Nummer eins und testen das Denken. Obwohl die schnelle Sortierung asymptotisch schneller ist als die Einfügungssortierung, ist die Einfügungssortierung bei extrem kleinen Eingaben tatsächlich schneller.

Guvante
quelle
2
Großartig ... gut gesagt ... "Testen schlägt Denken". Vielen Dank.
Praveen Prajapati
7

Die einzige Erklärung könnte sein, dass die CLR zusätzliche Optimierungen vornimmt (korrigieren Sie mich, wenn ich hier falsch liege).

Ja, es heißt Inlining. Dies erfolgt im Compiler (Maschinencode-Ebene - dh JIT). Da der Getter / Setter trivial ist (dh sehr einfacher Code), werden die Methodenaufrufe zerstört und der Getter / Setter in den umgebenden Code geschrieben.

Dies geschieht nicht im Debug-Modus, um das Debuggen zu unterstützen (dh die Möglichkeit, einen Haltepunkt in einem Getter oder Setter festzulegen).

Im Visual Studio gibt es keine Möglichkeit, dies im Debugger zu tun. Kompilieren Sie die Version, führen Sie sie ohne angehängten Debugger aus und Sie erhalten die vollständige Optimierung.

Ich glaube nicht, dass in einer realen Anwendung, in der diese Eigenschaften auf viel komplexere Weise verwendet werden, sie auf die gleiche Weise optimiert werden.

Die Welt ist voller Illusionen, die falsch sind. Sie werden optimiert, da sie noch trivial sind (dh einfacher Code, also inline).

TomTom
quelle
danke ... aber bitte hinterlasse die Kommentare zum Debuggen. Ich bin nicht so dumm zu versuchen, Debug-Builds für die Leistung zu vergleichen ..
Prost
Die sind sehr gültig, denn wenn Sie sich mit komplizierteren Dingen beschäftigen, werden Sie viele kleine Thigns finden, die sich anders verhalten. Der GC für Beispiel-Deos räumt auch NICHT viele Dinge schnell auf. Bewahrt Referenzen viel länger auf.
TomTom
4

Es sollte beachtet werden, dass es möglich ist, die "echte" Leistung in Visual Studio zu sehen.

  1. Kompilieren Sie im Release-Modus mit aktivierten Optimierungen.
  2. Gehen Sie zu Debug -> Optionen und Einstellungen und deaktivieren Sie "JIT-Optimierung beim Laden des Moduls unterdrücken (nur verwaltet)".
  3. Deaktivieren Sie optional "Nur meinen Code aktivieren", da Sie sonst möglicherweise nicht in den Code eintreten können.

Jetzt ist die montierte Baugruppe auch mit angeschlossenem Debugger dieselbe, sodass Sie auf Wunsch die optimierte Demontage durchführen können. Dies ist wichtig, um zu verstehen, wie die CLR den Code optimiert.

Asik
quelle
guter Punkt. es ist jedoch zu aufwendig. Es ist einfacher, es vom Explorer aus zu starten (insbesondere, da VS den Befehl Ordner öffnen im Datei-Explorer hat;))
Boppity Bop
3
Wenn Sie jedoch debuggen und prüfen möchten, ob etwas inline ist oder nicht, müssen Sie den Debugger als separaten Schritt anhängen. Wenn Sie diese beiden Optionen deaktivieren, können Sie einfach F5 drücken, um Ihren optimierten Build zu debuggen und den generierten Assemblycode einzugeben.
Asik
2

Nachdem ich alle Ihre Artikel gelesen habe, entscheide ich mich, mit diesem Code einen Benchmark zu erstellen:

    [TestMethod]
    public void TestFieldVsProperty()
    {
        const int COUNT = 0x7fffffff;
        A a1 = new A();
        A a2 = new A();
        B b1 = new B();
        B b2 = new B();
        C c1 = new C();
        C c2 = new C();
        D d1 = new D();
        D d2 = new D();
        Stopwatch sw = new Stopwatch();

        long t1, t2, t3, t4;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            a1.P = a2.P;
        }

        sw.Stop();

        t1 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            b1.P = b2.P;
        }

        sw.Stop();


        t2 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            c1.P = c2.P;
        }

        sw.Stop();

        t3 = sw.ElapsedTicks;

        sw.Restart();
        for (int i = COUNT - 1; i >= 0; i--)
        {
            d1.P = d2.P;
        }

        sw.Stop();


        t4 = sw.ElapsedTicks;
        long max = Math.Max(Math.Max(t1, t2), Math.Max(t3, t4));

        Console.WriteLine($"auto: {t1}, {max * 100d / t1:00.00}%.");
        Console.WriteLine($"field: {t2}, {max * 100d / t2:00.00}%.");
        Console.WriteLine($"manual: {t3}, {max * 100d / t3:00.00}%.");
        Console.WriteLine($"no inlining: {t4}, {max * 100d / t4:00.00}%.");

    }
    class A
    {
        public double P { get; set; }
    }
    class B
    {
        public double P;
    }
    class C
    {
        private double p;
        public double P
        {
            get => p;
            set => p = value;
        }
    }
    class D
    {
        public double P
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get;
            [MethodImpl(MethodImplOptions.NoInlining)]
            set;
        }
    }

Beim Test im Debug-Modus habe ich folgendes Ergebnis erhalten:

auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.

Wenn Sie jedoch in den Freigabemodus wechseln, ist das Ergebnis anders als zuvor.

auto: 2161291, 873.91%.
field: 2886444, 654.36%.
manual: 2252287, 838.60%.
no inlining: 18887768, 100.00%.

scheint Auto-Eigentum ist ein besserer Weg.

Dexiang
quelle