Warum ist TypeDreferenz hinter den Kulissen? Es ist so schnell und sicher ... fast magisch!

StackOverflow https://stackoverflow.com/questions/4764573

  •  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.)

War es hilfreich?

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 -Anweisungen arglist, refanytype, mkrefany, und refanyval soll das werfen System.NotImplementedException Ausnahme. Der genaue Zeitpunkt der Ausnahme ist nicht angegeben. Der Typ System.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:

  1. 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.
  2. 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top