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;
}
}
quelle
Antworten:
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.
quelle
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:
quelle
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.
quelle
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.
Die Welt ist voller Illusionen, die falsch sind. Sie werden optimiert, da sie noch trivial sind (dh einfacher Code, also inline).
quelle
Es sollte beachtet werden, dass es möglich ist, die "echte" Leistung in Visual Studio zu sehen.
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.
quelle
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.
quelle