MemberInfo.MetadataToken - Doch nicht unique?

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von WeLoveBurgers.

    MemberInfo.MetadataToken - Doch nicht unique?

    Hey,
    Ich habe ein Verfahren, welches anhand des types im typenparameter eine bestimmte abfolge von Methoden aufruft (Lambdas in einer dictionary).
    Nun habe ich als unique identifier den MetadataToken genommen, da ich mir gedacht habe, dass dieser eindeutig ist, wie ich aber zu meiner Verwunderung feststellen muss, kommt es irgendwie zu überlagerungen.
    Gibt es dazu Erklärung, bzw Vorschläge wie ich ein solches Verfahren ähnlich performance effizient aber sicher gestalten könnte?
    Mfg
    Bilder
    • Screenshot_23.png

      8,44 kB, 1.524×164, 198 mal angesehen
    Wer Rechtschreibfehler findet darf sie behalten :)
    @WeLoveBurgers Wähle eine weiteree Spalte als unique identifier aus, dann sollte die Kombination allerdings wirklich unique sein.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany wie meinst du das?
    @~blaze~ Im Grunde habe ich eine Klasse geschrieben, die von einem BinaryReader erbt und habe die Read Funktionen in einer generic Funktion vereint, diese sucht sich halt immer die richtige Readfunktion, je nach Rückgabetyp per reflection raus (z.B: byte - reader.ReadByte()). Dann hat man eine Funktion wo man den Typen angeben will und die funktion macht den Rest(myreader.ReadType<byte>()). Um nicht immer die richtige Submethode zu evaluieren, cache ich die Methodinfo in einer Dictionary, welche ich (bis vor kurzem) per Methodinfo des zurückzugebenen Typens speichere.
    Das hat bisher immer gut geklappt und hat auch mehrere Vorteile. Zum Beispiel kann ich jetzt eine komplette Poco Klasse "Cachen" und kann somit dem wrapper sagen, wie er diese zu lesen hat, und kann anschließend so etwas machen: reader.ReadType<MyCoolClass>();
    Momentan cache ich per FullName,da eine Dictionary ziemlich gut damit umgehen kann, aber ich seh mir mal den Modul-MetadataToken an.
    Mit einem MetadataToken konnte ich jedenfalls die zuordnung zu einer Methode mehrere millionen mal pro sekunde durchführen, was es ziemlich erstrebenswert macht :P
    Wer Rechtschreibfehler findet darf sie behalten :)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „WeLoveBurgers“ ()

    @ErfinderDesRades Aus selbstdurchgeführten performance Tests weis ich, dass ein Dictionary(Of Type, Dataset) im vergleich zu Dictionary(of int, Dataset) richtig inperformant ist, ich meine mich zu erinnern, dass es um einen faktor von 1000:1 langsamerer war.
    Wer Rechtschreibfehler findet darf sie behalten :)
    Tatsächlich ist das nicht mal sooo weit hergeholt, je nachdem, wie GetHashCode und Equals implementiert sind. Für Type sind die allerdings nicht allzu teuer, wenn auch nicht so günstig, wie für int. Man kann sich dazu ja den Reference Sourcecode anschauen (habe mir gerade EqualityComparer<T> und Type.GetHashCode angeschaut. Teuer sind die nicht).

    Sowas könnte man auch über Generics lösen:

    C#-Quellcode

    1. internal static class GenericBinaryAccess<T>
    2. {
    3. public static T Read(BinaryReader binaryReader)
    4. {
    5. ...
    6. }
    7. public static void Write(BinaryWriter binaryWriter, T value)
    8. {
    9. ...
    10. }
    11. }


    Und dann halt einmal initialisieren. Dann sparst du dir das Dictionary. Für nicht-generische Aufrufe ist das dann halt nicht gut geeignet, weshalb du wieder auf ein Dictionary zurückgreifen müsstest. Heißt aber nicht, dass die Modelle inkompatibel zueinander sind.

    @ErfinderDesRades
    Vergiss nicht, dass es Vorgänge gibt, in denen Optimalität nicht allzu schwer zu erreichen ist und der Code nicht verunstaltet wird. Ebenso sind Streams ja nicht pauschal Dateien. Wenn ich MemoryStreams verwende, kann es schon mal vorkommen, dass ich mich ärgere, wenn die Sachen "ewig" brauchen.

    Hat das mit dem Modul-Token denn jetzt geklappt? Ich habe gesehen, dass du das Hilfreich wieder entzogen hattest, also eher nein?

    Viele Grüße
    ~blaze~
    jo, evtl. sind seine BinaryReader ja für MemoryStreams vorgesehen, das wäre natürlich schneller. Aber ob so schnell, dass der Reader nicht mehr der Flaschenhals inne Verarbeitungskette ist - das sollte man erstmal untersuchen, bevor man fett in Mikro-Optimierung einsteigt.

    Weil das ist das Prinzip: Optimierungen woanders als am Flaschenhals sind vollkommen wirkungslos.
    Klar, aber es kann eine Art "Vorwärtsvermeidung" sein. Wenn es einfach nur eine Kleinigkeit ist, die man jetzt schon optimieren kann, kann man sich später Arbeit abnehmen - wenn man bspw. weiß, dass man das eh noch vorhat, ist es ja nicht sinnvoll, das auf später zu verlagern (sofern's eh nicht mehr als ein paar Codezeilen sind). Ich hätte bspw. direkt Streams verwendet, anstatt auf BinaryWriter/-Reader auszuweichen.

    Viele Grüße
    ~blaze~
    @ErfinderDesRades Wenn ich mich richtig erinnere waren es ~2 Minuten für 100Millionen zugriffe wohingegen ein integer 17 sekunden Braucht. Und naja das wird zum flaschenhals, habs testweise auf Type geändert und mein sample arbeitet deutlich langsamer(mittels Stopwatch und UserInterface).
    Nun, wie kommen jetzt so horrende Dict-Aufrufe zustande? Ganz einfach: Wenn ich, wie gesagt ganze Klassen strukturen auslese und die unter anderem auch sich untereinander rekursiv aufrufen, dann ist das nichtmal mehr so abwegig.

    @~blaze~ Ich kann mich nicht erinnern dir eins gegeben zu haben OO Hier hast trotzdem eins, falls ichs wirklich ausversehen wieder weggenommen hab :P
    Modul Token und deinen vorschlag sehe ich mir morgen in ruhe nochmal an bezüglich umsetzbarkeit, dafür bin ich jetzt echt zu müde (hatte heute auch nicht richtig zeit zu coden)
    Zur Zeit arbeite ich temporär mit der Fullname property.

    Zu dem BinaryReader: ich arbeite mit sowie mit Files als auch mit MemoryStreams gleichermaßen und finde das arbeiten mit diesem Stream-Wrapper richtig angenehm, deswegen habe ich diesen gewählt.


    Edit: Ok, der Gedanke deiner dict-less methode hat mich nicht mehr schlafen lassen, hier ist was ich bis jetzt hab, nur das Problem ist: Was, wenn ich eigene (nicht-statische) methoden über TBinaryAccessor.RegisterExtension registrieren will? Also wenn ich jetzt beispielsweise ein und die selbe poco/daten -klasse bei verschiedenen streams/files verschieden auslesen will. Wie kann ich das dann realisieren?

    Was ich bis jetzt hab- Gekürzte Fassung:
    Spoiler anzeigen

    C#-Quellcode

    1. public class TBAWrapper : IDisposable
    2. {
    3. private BinaryReader _br { get; set; }
    4. public TBAWrapper(Stream input) => Init(new BinaryReader(input));
    5. public TBAWrapper(BinaryReader input) => Init(input);
    6. private void Init(BinaryReader input) => _br = input;
    7. ~TBAWrapper() => Dispose();
    8. public void Dispose()
    9. {
    10. _br.Close();
    11. _br.Dispose();
    12. }
    13. public T Read<T>() => TBinaryAccessor<T>.Read(_br);
    14. }
    15. public static class TBinaryAccessor<T>
    16. {
    17. private static Func<BinaryReader, T> _storageFunc;
    18. //Registriert eigenen "Weg" wie man einen bestimmten datentyp ausliest (kann auch eine klassenstruktur sein :))
    19. public static void RegisterExtension(Func<BinaryReader, T> wrappingFunc) => _storageFunc = wrappingFunc;
    20. //_storageFunc == null ? Funktion reevaluieren ; Anschließend ausführen
    21. public static T Read(BinaryReader binaryReader){}
    22. }

    Wer Rechtschreibfehler findet darf sie behalten :)

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „WeLoveBurgers“ ()

    Umsetzbar sollte das auf jeden Fall sein: Einfach auf ulong umsteigen und als Key eben unchecked(((ulong)(uint)moduleToken << 32) | (ulong)(uint)typeToken)) verwenden.

    Für den Rest habe ich gerade keinen Kopf mehr. ;)

    Ich freue mich btw. zwar über "Hilfreich"s, aber nur, wenn sie freiwillig und gerechtfertigt sind. ;)

    Viele Grüße
    ~blaze~

    WeLoveBurgers schrieb:

    wie meinst du das?
    Zwei Spalten mit PrimaryKey = True.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    a << b ist einfach nur: Verschiebe alle Bits in a um b nach links. c >> d ist entsprechend: Verschiebe alle Bits in c um d nach rechts. Was überhängt, wird abgeschnitten.

    Z.B.
    1000 >> 2 = 0010
    1010 >> 1 = 0101

    1010 << 1 = 0100
    1000 << 1 = 0000

    Aufpassen sollte man bei Datentypen, die ein Vorzeichen haben: Bei Rechtsshifts >> bei Short, Integer und Long wird bei negativen Werten mit 1 aufgefüllt (arithmetischer Shift). Dieses Verhalten trifft auf UShort, usw. nicht zu, dort wird stets mit 0 aufgefüllt (logischer Shift).

    Mach' bei der BinaryAccessor-Klasse einfach folgendes:

    C#-Quellcode

    1. public static class BinaryAccessor<T>
    2. {
    3. private static Func<T, byte[]> _getBytesHandler;
    4. private static Func<byte[], T> _getInstanceHandler;
    5. public static byte[] GetData(T instance)
    6. {
    7. var handler = _getBytesHandler;
    8. if (handler == null)
    9. Throw new ArgumentException("Data cannot be determined for type " + typeof(T).FullName + ".");
    10. return _handler (instance);
    11. }
    12. public static T GetInstance(byte[] data)
    13. {
    14. var handler = _getInstanceHandler;
    15. if (handler == null)
    16. Throw new ArgumentException("Instance cannot be determined for type " + typeof(T).FullName + ".");
    17. return _handler (instance);
    18. }
    19. static BinaryAccessor()
    20. {
    21. //Hier suchst du eine Lösung
    22. Type tp = typeof(T);
    23. if (tp.IsPrimitive)
    24. {
    25. //Daten für den entsprechenden Typen herausfinden
    26. }
    27. }
    28. }

    Es gilt dann halt, den Delegaten einmal zu erzeugen, was auch durchaus ineffizient sein darf, solange der Delegat an sich effizient ist. Was ein wenig problematisch ist, ist, die Routine für die beiden Delegaten _getBytesHandler und _getInstanceHandler zu ermitteln.
    Da C# es nicht unterstützt, Rohdaten von Generika direkt auszulesen - auch nicht über unsafe - kann das recht hässlich werden. Ich hatte da gestern bei einem meiner Projekte genau dieses Problem. Eine ineffiziente Möglichkeit ist, es über Boxing handzuhaben:
    - den Parameter in object ablegen
    - GCHandle dazu verwenden, das Objekt anzupinnen
    - Mit Marshal.Copy die Daten in ein Array der größe Marshal.SizeOf(typeof(T)) kopieren
    Das Boxing im ersten Schritt ist halt extrem teuer im Vergleich zur Ausleseoperation an sich. Falls dir Boxing nichts sagt: Es bezeichnet das Ablegen eines Wertetyps in object (bzw. ValueType). Dabei wird der Wertetyp als Referenztyp abgelegt und so getan, als handle es sich um eine Referenz. Das erfordert allerdings, dass im Heap Speicher für diese Instanz angelegt wird und das ist teuer (es entspricht dem Aufruf des Konstruktors von einem Referenztyp).

    Für Fortgeschrittene
    Ich hatte es außerdem mit stackalloc versucht, habe aber kein gutes Ergebnis erhalten. Die Idee war, mit stackalloc einen Speicherbereich auf dem Stack zu erhalten und hinter diesem Speicherbereich eine Variable vom Typ T anzulegen und über den Speicherbereich der stackalloc-Adresse hinauszulesen. Hat aber eben nicht geklappt auch nicht mit Alignmentversuchen. Hatte jetzt aber auch keine Zeit, mir das nochmal anzuschauen.
    Was mir noch eingefallen wäre, wäre, ein Struct anzulegen mit einer fixed-Variablen und explizitem Layout, sodass man die Adressen beider Variablen auf 0 relativ zum struct setzen kann und daraus die Daten auszulesen. Ich weiß halt nur nicht, ob das Alignment einem einen Strich durch die Rechnung macht. Marshal.SizeOf kalkuliert, wenn ich mich recht erinnere, mit Alignment, was für diesen Fall sehr schlecht wäre; aber sicher bin ich mir da gerade nicht. Der sizeof-Operator an sich kann wiederum wieder keine Generika. An dieser Stelle könnte man allerdings einen Delegaten generieren, der genau das eben durchführt.
    Falls dir Alignment nichts sagt: Speicher wird von vielen Prozessoren auf 4, 8 oder 16 Bytes (16 Bytes wäre bei den modernen 64-Bit Prozessoren afaik die Regel) erweitert. Das hängt mit dem Speicherzugriff zusammen und wird durch die Breite des Datenbusses bestimmt.


    Viele Grüße
    ~blaze~
    @RodFromGermany Sorry, ich weis nicht wo ich das machen soll Oo
    @~blaze~
    Holy fu*king sh*t! Danke! Also ich hab jetzt die ladezeiten dank dir und dotTrace von jetbrains von 10sec+ auf ganze 5 sec runtergekürzt, wobei beim test 26.545.529 lesezugriffe passieren. Eines der Bottlenecks war nichtmal das dictionary, es war eher das Methodinfo.Invoke.
    Ich nutze jetzt eine Mischung aus Dictionary und Generics. Wobei die Read Funktion in meiner eigenen klasse mittels typeOfT.IsPrimitive nachsieht ob es ein primitiver datentyp ist. Alle Komplexen klassen werden eben instanzintern abgehandelt. Beim BinaryAccessor suche ich mir mittels reflection die methodinfo raus und caste sie mir mittels Methodinfo.CreateDelegate zu nem handlichen Func<BinaryReader, T> - Delegate
    Bilder
    • Screenshot_3.png

      19,12 kB, 628×405, 285 mal angesehen
    • Screenshot_5.png

      20,47 kB, 271×308, 139 mal angesehen
    Wer Rechtschreibfehler findet darf sie behalten :)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „WeLoveBurgers“ ()