Warum ist TypeDreferenz hinter den Kulissen? Es ist so schnell und sicher ... fast magisch!
-
16-10-2019 - |
Frage
WARNUNG: Diese Frage ist ein bisschen ketzerisch ... religiöse Programmierer bleiben immer gute Praktiken. Bitte lesen Sie sie nicht. :)
Weiß jemand, warum der Gebrauch von TypedReference ist so entmutigt (implizit durch mangelnde Dokumentation)?
Ich habe große Verwendungszwecke dafür gefunden, z. object
Möglicherweise übertrieben oder langsam sein, wenn Sie einen Werttyp benötigen), wenn Sie einen undurchsichtigen Zeiger benötigen oder wenn Sie schnell auf ein Element eines Arrays zugreifen müssen, dessen Spezifikationen Sie zur Laufzeit finden (mit Verwendung Array.InternalGetReference
). Warum ist sie entmutigt, da die CLR nicht einmal eine falsche Verwendung dieses Typs zulässt? Es scheint nicht unsicher zu sein oder so ...
Andere Verwendungen, für die ich gefunden habe TypedReference
:
"Specialization" Generics in C# (dies ist Typ-Safe):
static void foo<T>(ref T value)
{
//This is the ONLY way to treat value as int, without boxing/unboxing objects
if (value is int)
{ __refvalue(__makeref(value), int) = 1; }
else { value = default(T); }
}
Schreiben von Code, der mit generischen Zeigern funktioniert (dies ist dies sehr Unsicher, wenn sie missbraucht, aber schnell und sicher, wenn sie richtig verwendet werden):
//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It's dangerous if you don't know what you're doing, but very worth if you do.
static T Read<T>(IntPtr address)
{
var obj = default(T);
var tr = __makeref(obj);
//This is equivalent to shooting yourself in the foot
//but it's the only high-perf solution in some cases
//it sets the first field of the TypedReference (which is a pointer)
//to the address you give it, then it dereferences the value.
//Better be 10000% sure that your type T is unmanaged/blittable...
unsafe { *(IntPtr*)(&tr) = address; }
return __refvalue(tr, T);
}
Schreiben ein Methode Version des sizeof
Anweisung, die gelegentlich nützlich sein kann:
static class ArrayOfTwoElements<T> { static readonly Value = new T[2]; }
static uint SizeOf<T>()
{
unsafe
{
TypedReference
elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
unsafe
{ return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
}
}
Schreiben Sie eine Methode, die einen "Status" -Parameter übergibt, der das Boxen vermeiden möchte:
static void call(Action<int, TypedReference> action, TypedReference state)
{
//Note: I could've said "object" instead of "TypedReference",
//but if I had, then the user would've had to box any value types
try
{
action(0, state);
}
finally { /*Do any cleanup needed*/ }
}
Warum werden solche "entmutigt" verwendet (durch mangelnde Dokumentation)? Gibt es besondere Sicherheitsgründe? Es scheint absolut sicher und überprüfbar, wenn es nicht mit Zeigern gemischt ist (die sowieso nicht sicher oder überprüfbar sind) ...
Aktualisieren:
Beispielcode, um das zu zeigen, in der Tat, TypedReference
kann doppelt so schnell (oder mehr) sein:
using System;
using System.Collections.Generic;
static class Program
{
static void Set1<T>(T[] a, int i, int v)
{ __refvalue(__makeref(a[i]), int) = v; }
static void Set2<T>(T[] a, int i, int v)
{ a[i] = (T)(object)v; }
static void Main(string[] args)
{
var root = new List<object>();
var rand = new Random();
for (int i = 0; i < 1024; i++)
{ root.Add(new byte[rand.Next(1024 * 64)]); }
//The above code is to put just a bit of pressure on the GC
var arr = new int[5];
int start;
const int COUNT = 40000000;
start = Environment.TickCount;
for (int i = 0; i < COUNT; i++)
{ Set1(arr, 0, i); }
Console.WriteLine("Using TypedReference: {0} ticks",
Environment.TickCount - start);
start = Environment.TickCount;
for (int i = 0; i < COUNT; i++)
{ Set2(arr, 0, i); }
Console.WriteLine("Using boxing/unboxing: {0} ticks",
Environment.TickCount - start);
//Output Using TypedReference: 156 ticks
//Output Using boxing/unboxing: 484 ticks
}
}
(Bearbeiten: Ich habe den Benchmark oben bearbeitet, da die letzte Version des Beitrags eine Debug -Version des Codes verwendet hat [ich habe vergessen, ihn in die Veröffentlichung zu ändern] und keinen Druck auf die GC ausübt. Diese Version ist etwas realistischer und Auf meinem System ist es mehr als dreimal schneller mit TypedReference
im Durchschnitt.)
Lösung
Kurze Antwort: Portabilität.
Während __arglist
, __makeref
, und __refvalue
sind Sprachverlängerungen und sind in der C# -sprachspezifikation undokumentiert, die Konstrukte, die zur Implementierung unter der Haube verwendet werden (vararg
Konventur rufen, TypedReference
Typ, arglist
, refanytype
, mkanyref
, und refanyval
Anweisungen) sind in der perfekt dokumentiert CLI-Spezifikation (ECMA-335) in dem Vararg Library.
Wenn Sie in der Vararg-Bibliothek definiert werden, wird deutlich, dass sie in erster Linie Argumentlisten mit variabler Länge und nicht viel anderes unterstützen sollen. Variable-Argument-Listen werden in Plattformen, die nicht mit einem externen C-Code mit Varargs verknüpfen, wenig verwendet. Aus diesem Grund ist die Varargs -Bibliothek nicht Teil eines CLI -Profils. Legitime CLI -Implementierungen können sich dafür entscheiden, die Varargs -Bibliothek nicht zu unterstützen, da sie nicht im Cli -Kernel -Profil enthalten ist:
4.1.6 Vararg
Das Vararg -Funktionssatz Unterstützt Argumentenlisten mit variabler Länge und Runtime-Typ-Zeiger.
Wenn weggelassen: Jeder Versuch, auf eine Methode mit der zu verweisen
vararg
Aufrufkonvention oder die Signaturcodierungen, die mit Vararg -Methoden verbunden sind (siehe Partition II)System.NotImplementedException
Ausnahme. Methoden mit den CIL -Anweisungenarglist
,refanytype
,mkrefany
, undrefanyval
soll das werfenSystem.NotImplementedException
Ausnahme. Der genaue Zeitpunkt der Ausnahme ist nicht angegeben. Der TypSystem.TypedReference
muss nicht definiert werden.
Update (Antwort auf GetValueDirect
Kommentar):
FieldInfo.GetValueDirect
sind FieldInfo.SetValueDirect
sind nicht Teil der Basisklassenbibliothek. Beachten Sie, dass es einen Unterschied zwischen .NET Framework -Klassenbibliothek und Basisklassenbibliothek gibt. BCL ist das einzige, was für eine konforme Implementierung des CLI/C# erforderlich ist und in dokumentiert ist ECMA TR/84. (In der Tat, FieldInfo
selbst ist Teil der Reflexionsbibliothek und das ist auch nicht im Cli -Kernel -Profil enthalten).
Sobald Sie eine Methode außerhalb von BCL anwenden, geben Sie ein wenig Portabilität auf (und dies wird immer wichtiger mit dem Aufkommen von Nicht-NET-CLI-Implementierungen wie Silverlight und Monotouch). Selbst wenn eine Implementierung die Kompatibilität mit der Microsoft .NET Framework Class Library erhöhen wollte, kann sie einfach bereitstellen GetValueDirect
und SetValueDirect
ein Nehmen TypedReference
ohne das zu machen TypedReference
speziell von der Laufzeit behandelt (im Grunde genommen, was sie gleichbedeutend mit ihrem entspricht object
Gegenstücke ohne Leistungsnutzen).
Hätten sie es in C#dokumentiert, hätte es zumindest ein paar Auswirkungen gehabt:
- Wie jede Funktion, es kann Werden Sie eine Straßensperre für neue Funktionen, zumal dieses nicht wirklich in das Design von C# passt und seltsame Syntax -Erweiterungen und spezielle Übergabe eines Typs zur Laufzeit erfordert.
- Alle Implementierungen von C# müssen diese Funktion irgendwie implementieren, und es ist nicht unbedingt trivial/möglich für C# Implementierungen, die überhaupt nicht auf einer CLI ausgeführt werden oder auf einer CLI ohne Varargs ausgeführt werden.
Andere Tipps
Nun, ich bin kein Eric Lippert, also kann ich nicht direkt von Microsofts Motivationen sprechen, aber wenn ich eine Vermutung wagen würde, würde ich das sagen TypedReference
et al. sind nicht gut dokumentiert, weil Sie sie ehrlich gesagt nicht brauchen.
Jede Verwendung, die Sie für diese Funktionen erwähnt haben, kann ohne sie erreicht werden, wenn auch in einigen Fällen eine Leistungsstrafe. Aber C# (und .NET im Allgemeinen) ist nicht als Hochleistungssprache ausgelegt. (Ich vermute, dass "schneller als Java" das Leistungsziel war.)
Das heißt nicht, dass bestimmte Leistungsüberlegungen nicht gewährt wurden. In der Tat wie Merkmale wie Zeiger, stackalloc
, und bestimmte optimierte Rahmenfunktionen bestehen weitgehend, um die Leistung in bestimmten Situationen zu steigern.
Generika, die ich sagen würde, haben das primär Nutzen der Art von Sicherheit, verbessern auch die Leistung ähnlich wie TypedReference
durch Vermeiden von Boxen und Unboxen. Tatsächlich habe ich mich gefragt, warum Sie das bevorzugen:
static void call(Action<int, TypedReference> action, TypedReference state){
action(0, state);
}
dazu:
static void call<T>(Action<int, T> action, T state){
action(0, state);
}
Wie ich sehe, sind die Kompromisse, dass Ersterer weniger Jits benötigt (und, wie folgt, weniger Gedächtnis), während letzteres eher vertraut ist und, wie ich angenommen würde, etwas schneller (durch Vermeiden von Zeiger Dereferenzierung).
Ich würde anrufen TypedReference
und Implementierungsdetails für Freunde. Sie haben auf einige ordentliche Verwendungen für sie hingewiesen, und ich denke, sie lohnt sich, es zu erkunden, aber die übliche Einschränkung, sich auf Implementierungsdetails zu verlassen, gilt - die nächste Version kann Ihren Code brechen.
Ich kann nicht herausfinden, ob der Titel dieser Frage sarkastisch sein soll: Es war es Langsteuert das TypedReference
ist der langsame, aufgeblähte, hässliche Cousin von 'wahren' verwalteten Zeigern, wobei letztere das ist, was wir bekommen C ++/cli interior_ptr<T>
, oder sogar traditionelle Untersuchung (ref
/out
) Parameter in C#. In der Tat ist es ziemlich schwer zu machen TypedReference
Erreichen Sie auch die Basisleistung, wenn Sie nur eine Ganzzahl verwenden, um das ursprüngliche CLR-Array jedes Mal neu zu investieren.
Die traurigen Details sind hier, aber Zum Glück ist nichts davon jetzt wichtig ...
Diese Frage wird nun von der Neuen streit Ref -Einheimische und Ref Return Funktionen in C# 7
Diese neuen Sprachmerkmale bieten eine prominente, erstklassige Unterstützung in C# zum Erklären, Teilen und Manipulieren der wahren CLR
verwalteter Referenztyp-typen in sorgfältig präziierten Situationen.
Die Verwendungsbeschränkungen sind nicht strenger als das, was zuvor benötigt wurde TypedReference
(Und die Aufführung ist buchstäblich springen vom schlimmsten zum besten), also sehe ich keinen verbleibenden denkbaren Anwendungsfall in C# zum TypedReference
. Zum Beispiel gab es zuvor keine Möglichkeit, a zu bestehen TypedReference
in dem GC
Haufen, also ist das gleiche für die überlegenen, verwalteten Zeiger, die jetzt kein Mitnehmen ist.
Und offensichtlich der Niedergang von TypedReference
- oder zumindest seine fast vollständige Abwertung __makeref
Auch auf dem Junkheap.