Performance-Überraschung mit "as" und NULL-fähigen Typen
Ich bin nur eine überarbeitung Kapitel 4 in C# in der Tiefe, die sich mit nullable-Typen, und ich bin das hinzufügen einen Abschnitt über die Verwendung des "as" - operator, welcher Ihnen erlaubt, zu schreiben:
object o = ...;
int? x = o as int?;
if (x.HasValue)
{
... //Use x.Value in here
}
Dachte ich, das war wirklich nett, und es könnte verbessern Sie die Leistung über die C# 1 entspricht, mit "" gefolgt von einer Besetzung - nach allen, diesen Weg, wir brauchen Sie nur zu Fragen für die dynamische Typ-überprüfung einmal, und dann einen einfachen Wert überprüfen.
Dies scheint nicht der Fall zu sein, jedoch. Ich habe einen Probe-test-app unter, die im Grunde die Summen die zahlen in einem object-array, aber das array enthält eine Menge von null-Referenzen und die string-Referenzen als auch als boxed-Ganzzahlen. Der benchmark misst den code müssten Sie zur Verwendung in C# 1, den code über die "wie" - operator, und einfach nur aus Spaß eine LINQ Lösung. Zu meinem Erstaunen, die C# 1 code 20 mal schneller-in diesem Fall - und auch die LINQ-code (was ich erwartet habe, langsamer zu sein, da die Iteratoren beteiligt) schlägt das "wie" code.
Ist .NET-Implementierung der isinst
für nullable-Typen nur sehr langsam? Ist es das zusätzliche unbox.any
die das problem verursacht? Gibt es eine andere Erklärung dafür? Im moment fühlt es sich wie ich bin zu haben, um eine Warnung gegen die Verwendung dieser Leistung in heiklen Situationen...
Ergebnisse:
Darsteller: 10000000 : 121
Als: 10000000 : 2211
LINQ: 10000000 : 2143
Code:
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size - 2; i += 3)
{
values[i] = null;
values[i+1] = "";
values[i+2] = 1;
}
FindSumWithCast(values);
FindSumWithAs(values);
FindSumWithLinq(values);
}
static void FindSumWithCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
foreach (object o in values)
{
if (o is int)
{
int x = (int) o;
sum += x;
}
}
sw.Stop();
Console.WriteLine("Cast: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
static void FindSumWithAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
foreach (object o in values)
{
int? x = o as int?;
if (x.HasValue)
{
sum += x.Value;
}
}
sw.Stop();
Console.WriteLine("As: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
static void FindSumWithLinq(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = values.OfType<int>().Sum();
sw.Stop();
Console.WriteLine("LINQ: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
}
InformationsquelleAutor der Frage Jon Skeet | 2009-10-17
Du musst angemeldet sein, um einen Kommentar abzugeben.
Klar der Maschinen-code wird der JIT-compiler erzeugen kann, für die der erste Fall ist sehr viel effizienter. Eine Regel, die wirklich hilft, es ist, dass ein Objekt nur ohne Verpackung in eine variable, die denselben Typ wie der boxed Wert. Sie ermöglicht, dass der JIT-compiler erzeugen sehr effizienten code, kein Wert der conversions berücksichtigt werden müssen.
Den ist operator test ist einfach, einfach prüfen, ob das Objekt nicht null ist und ist von dem erwarteten Typ ist, dauert aber noch ein paar Maschinencode-Anweisungen. Der cast ist auch einfach, der JIT-compiler kennt die Position der Wert-bits im Objekt-und nutzt Sie direkt. Die Vervielfältigung oder Konvertierung stattfindet, werden alle Maschinen-code ist inline-und dauert aber etwa ein Dutzend Anweisungen. Dieser benötigt, um wirklich effizient zurück .NET 1.0 beim Boxen üblich war.
Casting nach int? braucht viel mehr Arbeit. Der Wert Darstellung der boxed integer ist nicht kompatibel mit der Speicher-layout von
Nullable<int>
. Eine Konvertierung erforderlich ist, und der code ist tricky wegen möglich, boxed enum-Typen. Der JIT-compiler erzeugt einen Anruf an einen CLR-helper-Funktion mit dem Namen JIT_Unbox_Nullable um den job zu erledigen. Dies ist eine Allgemeine Funktion für einen beliebigen Wert geben, viel code, es zu überprüfen-Typen. Und der Wert wird kopiert. Schwer einzuschätzen sind die Kosten, da dieser code gesperrt ist, innerhalb mscorwks.dll aber Hunderte von Maschinencode-Anweisungen wahrscheinlich ist.Linq-OfType () - Erweiterungsmethode verwendet auch die ist Betreiber und die Darsteller. Dies ist jedoch ein cast auf einen generischen Typ. Der JIT-compiler erzeugt ein Aufruf einer Hilfsfunktion, JIT_Unbox() ausführen kann ein cast auf einen beliebigen Wert zu geben. Ich habe keine großartige Erklärung, warum es ist so langsam wie der cast zu
Nullable<int>
angesichts der Tatsache, dass weniger Arbeit sein sollte, notwendig. Ich vermute, dass ngen.exe könnte Schwierigkeiten verursachen hier.InformationsquelleAutor der Antwort Hans Passant
Scheint es mir, dass die
isinst
ist einfach wirklich langsam auf null festlegbaren Typen. In der MethodeFindSumWithCast
ich geändertzu
welche auch deutlich verlangsamt die Ausführung. Den nur differenc in IL ich sehen kann, ist, dass
verändert wird, um
InformationsquelleAutor der Antwort Dirk Vollmar
Dieser begann ursprünglich als ein Kommentar zu Hans Passant ausgezeichnete Antwort, aber es hat zu lange, so möchte ich hinzufügen, dass ein paar bits hier:
Ersten, die C#
as
Betreiber Strahlen eineisinst
IL-Anweisung (also nicht dieis
Betreiber). (Eine andere interessante Anweisung istcastclass
emited, wenn Sie einen direkten cast und weiß der compiler, die runtime-überprüfung kann nicht weggelassen werden.)Hier ist, was
isinst
tut (ECMA 335-Partition III, 4.6):Allem:
So, die performance-killer ist nicht
isinst
in diesem Fall aber die zusätzlichenunbox.any
. Dies war nicht klar, aus Hans' Antwort, als er sah das JIT-Anpassung nur code. Im Allgemeinen, der C# - compiler emittiert wird einunbox.any
nach einemisinst T?
(aber weglassen, falls Sieisinst T
wennT
ist ein Referenz-Typ).Warum tut er das?
isinst T?
hat nie den Effekt, dass wäre klar gewesen, d.h. Sie erhalten wieder einT?
. Stattdessen werden alle diese Anweisungen sorgen dafür, dass Sie eine"boxed T"
werden können, ohne Verpackung zuT?
. Um eine tatsächlicheT?
müssen wir noch unbox unsere"boxed T"
zuT?
das ist der Grund, warum der compiler gibt eineunbox.any
nachisinst
. Wenn Sie darüber nachdenken, das macht Sinn, weil die "Feld-format" fürT?
ist nur ein"boxed T"
und machencastclass
undisinst
führen Sie die unbox unvereinbar wäre.Sichern Hans' Suche nach mit einigen Informationen aus der standardhier geht es:
(ECMA 335-Partition III, 4.33):
unbox.any
(ECMA 335-Partition III, 4.32):
unbox
InformationsquelleAutor der Antwort Johannes Rudolph
Interessanterweise habe ich weitergegeben feedback über operator-Unterstützung über
dynamic
als eine Größenordnung langsamerNullable<T>
(ähnlich dieses frühe testen) - ich vermute, dass bei sehr ähnlichen Gründen.Gotta love
Nullable<T>
. Ein weiterer Spaß ist, dass, obwohl die JIT-spots (und entfernt)null
für nicht-nullable structs, es borks es fürNullable<T>
:InformationsquelleAutor der Antwort Marc Gravell
Dies ist das Ergebnis FindSumWithAsAndHas oben: alt-text http://www.freeimagehosting.net/uploads/9e3c0bfb75.png
Dies ist das Ergebnis FindSumWithCast: alt-text http://www.freeimagehosting.net/uploads/ce8a5a3934.png
Ergebnisse:
Mit
as
es zuerst testen, ob ein Objekt eine Instanz von Int32; unter der Haube ist es mitisinst Int32
(das ist ähnlich wie hand-code geschrieben: if (o is int) ). Und mitas
es auch unbedingt unbox das Objekt. Und es ist ein echter performance-killer zu nennen, eine Eigenschaft(es ist immer noch eine Funktion unter der Haube), IL_0027Verwendung von cast, testen Sie zuerst, wenn Objekt ist eine
int
if (o is int)
; unter der Haube, diese ist mitisinst Int32
. Wenn es eine Instanz von int, dann können Sie sicher unbox der Wert, IL_002DEinfach ausgedrückt, ist dies der pseudo-code zu verwenden
as
Ansatz:- Und dies ist der pseudo-code der Verwendung von cast-Ansatz:
Also der cast (
(int)a[i]
sowie die syntax sieht aus wie aus einem Guss, aber es ist eigentlich unboxing, cast und unboxing teilen Sie die gleiche syntax, das nächste mal werde ich pedantisch mit der richtigen Terminologie) - Ansatz ist wirklich schneller, Sie brauchte nur zu unbox einen Wert, wenn Sie ein Objekt entschieden eineint
. Das gleiche kann nicht gesagt werden, um mit einemas
Ansatz.InformationsquelleAutor der Antwort Michael Buen
Profiling weiter:
Ausgabe:
Was können wir folgern aus diesen zahlen?
die Umsetzung ^_^
InformationsquelleAutor der Antwort
Ich habe keine Zeit, es zu versuchen, aber vielleicht möchten Sie haben:
als
Sind Sie ein neues Objekt erstellen jedes mal, die nicht komplett erklären, das problem, kann aber dazu beitragen.
InformationsquelleAutor der Antwort James Black
Habe ich versucht, den genauen Typ überprüfen konstruieren
typeof(int) == item.GetType()
die verhält sich so schnell wie dieitem is int
version, und gibt immer die Anzahl (Schwerpunkt: selbst wenn Sie schrieb eineNullable<int>
auf die Reihe, du brauchsttypeof(int)
). Sie müssen auch eine zusätzlichenull != item
hier überprüfen.Jedoch
typeof(int?) == item.GetType()
bleibt schnell (im Gegensatz zuitem is int?
), aber immer false zurück.Den typeof-Konstrukt ist in meinen Augen der Schnellste Weg für genaue Typ-überprüfung, wie es verwendet die RuntimeTypeHandle. Da die genauen Typen in diesem Fall nicht übereinstimmen mit null-Werte zulässt, meine Vermutung ist,
is/as
zu tun haben zusätzliche heavylifting hier auf, sicherzustellen, dass es ist in der Tat eine Instanz von Nullable-Typ.Und mal ehrlich: was macht Ihr
is Nullable<xxx> plus HasValue
kaufen Sie? Nichts. Sie können immer direkt auf der zugrunde liegenden (Wert -) Typ (in diesem Fall). Sie erhalten entweder den Wert, oder "Nein, nicht eine Instanz des Typs, den Sie forderten". Auch wenn Sie schrieb(int?)null
auf das array, der Typ check wird false zurückgegeben.InformationsquelleAutor der Antwort dalo
Ausgänge:
[EDIT: 2010-06-19]
Hinweis: der Bisherige test wurde gemacht, innen VS, Konfiguration, debug, mit VS2009, mit Core i7(Unternehmens-Entwicklung-Maschine).
Wurde Folgendes gemacht auf meinem Rechner mit Core 2 Duo, mit VS2010
InformationsquelleAutor der Antwort Michael Buen
In Ordnung zu halten, diese Antwort ist up-to-date, es ist erwähnenswert, dass die meisten die Diskussion auf dieser Seite ist nun moot jetzt mit C# 7.1 und .NETTO 4.7 unterstützt ein slim-syntax, die auch produziert die besten IL-code.
Den OP ' s original-Beispiel...
wird einfach...
Habe ich festgestellt, dass eine häufige Verwendung für die neue syntax ist beim schreiben ein .NET Wert Typ (d.h.
struct
im C#), dieIEquatable<MyStruct>
(die meisten sollten). Nach der Umsetzung des stark-typisierteEquals(MyStruct other)
- Methode können Sie nun, anmutig umleiten der nicht typisierteEquals(Object obj)
überschreiben (geerbt vonObject
) wie folgt:Anhang: Die
Release
bauen IL code für die ersten zwei Beispiel-Funktionen, siehe oben, in dieser Antwort (beziehungsweise) sind hier gegeben. Während der IL-code für die neue syntax ist in der Tat 1 byte kleiner ist, ist es meistens gewinnt big, indem Sie keine Anrufe (vs. zwei) und die Vermeidung derunbox
Betrieb insgesamt, wenn möglich.Zur weiteren Prüfung, das untermauert meine Bemerkung über die Leistung der neuen C#7 syntax übertrifft die bisher verfügbaren Optionen finden Sie unter hier (insbesondere, Beispiel 'D').
InformationsquelleAutor der Antwort Glenn Slayden