Zum Hauptinhalt springen

StoreFunctions und Allocator-Struct-Wrapper

Dieser Abschnitt behandelt beide, da sie Teil einer Änderung waren, um zwei zusätzliche Typparameter, TStoreFunctions und TAllocator, zu TsavoriteKV sowie den verschiedenen Sitzungen und *Context (z. B. BasicContext) hinzuzufügen. Der Zweck beider ist es, eine bessere Leistung durch Inlining von Aufrufen zu erzielen. StoreFunctions bietet auch ein besseres logisches Design für die Platzierung von Operationen, die Store-Ebene und nicht Sitzungsebene sind, wie unten beschrieben.

Aus Sicht des Aufrufers haben wir zwei neue Typparameter für TsavoriteKV<TKey, TValue, TStoreFunctions, TAllocator>. Die TStoreFunctions und TAllocator befinden sich auch auf *.Context (z. B. BasicContext). C# erlaubt den 'using'-Alias nur am Anfang einer Namespace-Deklaration, und der Alias ist dateilokal und wird von nachfolgenden 'using'-Aliassen erkannt, sodass die "Api"-Aliase wie BasicGarnetApi in mehreren Dateien jetzt viel länger sind.

Der Konstruktor von TsavoriteKV wurde geändert, um 3 Parameter zu akzeptieren

  • KVSettings<TKey, TValue>. Dies ersetzt die bisherige lange Liste von Parametern. LogSettings, ReadCacheSettings und CheckpointSettings sind zu internen Klassen geworden, die nur von TsavoriteKV (erstellt aus TsavoriteKVSettings) beim Instanziieren der Allocators verwendet werden (z. B. hat der neue AllocatorSettings ein LogSettings-Mitglied). SerializerSettings wurde zugunsten von Methoden auf IStoreFunctions entfernt.
  • Eine Instanz von TStoreFunctions. Diese wird normalerweise durch einen Aufruf einer statischen StoreFunctions-Factory-Methode erstellt, der die einzelnen zu enthaltenden Komponenten übergeben werden.
  • Eine Factory Func<> für die TAllocator-Instanziierung.

Diese werden unten detaillierter beschrieben.

StoreFunctions-Übersicht

StoreFunctions bezieht sich auf die Menge von Callback-Funktionen, die auf der Ebene von TsavoriteKV liegen, analog zu ISessionFunctions auf Sitzungsebene. Ähnlich wie bei ISessionFunctions gibt es eine IStoreFunctions. Die ISessionFunctions-Implementierung kann jedoch entweder eine Struktur oder eine Klasse sein. Tsavorite stellt die Klasse SessionFunctionsBase bereit, von der abgeleitet werden kann, als Dienstprogramm. Typparameter, die von Klassen implementiert werden, generieren jedoch keinen inlineden Code.

Da IStoreFunctions maximale Inlinierung ermöglichen soll, bietet Tsavorite keine StoreFunctionsBase. Stattdessen stellt Tsavorite eine StoreFunctions-Struktur-Implementierung mit optionalen übergebenen Implementierungen bereit für

  • Schlüsselvergleich (zuvor als ITsavoriteKeyComparer-Schnittstelle übergeben, die nicht inlined wird)
  • Schlüssel- und Wertserialisierer. Aufgrund von Einschränkungen bei Typparametern müssen diese als Func<> übergeben werden, die die Implementierungsinstanz erstellt, und da die Serialisierung eine teure Operation ist, bleiben wir bei den Schnittstellen IObjectSerializer<TKey> und IObjectSerializer<TValue>, anstatt die IStoreFunctions<TKey, TValue>-Schnittstelle mit den Schlüssel- und Wertserialisierer-Typparametern zu überladen.
  • Datensatzentsorgung (früher auf ISessionFunctions als mehrere Methoden und jetzt nur noch eine einzige Methode mit einem "reason"-Parameter).
  • Checkpoint-Abschluss-Callback (früher auf ISessionFunctions).

Natürlich kann dies, da TsavoriteKV den Typparameter TStoreFunctions hat, jeder vom Aufrufer implementierte Typ sein, einschließlich einer Klasseninstanz (die langsamer wäre).

Allocator-Wrapper-Übersicht

Wie bei StoreFunctions zielt der Allocator-Wrapper darauf ab, maximale Inlinierung zu ermöglichen. Wie oben erwähnt, generieren Typparameter, die von Klassen implementiert werden, keinen inlineden Code; der JITted-Code ist allgemein, für eine einzelne IntPtr-große Referenz. Für Strukturen generiert der JITter jedoch spezifischen Code für diesen bestimmten Strukturtyp, teilweise weil die Größe variieren kann (z. B. wenn sie als Parameter auf den Stack geschoben wird).

Es gibt einen Hack, der es ermöglicht, einen von einer Klasse implementierten Typparameter zu inlinen: Der generische Typ muss für eine Struktur sein, die den Klassentyp umschließt und Aufrufe auf diesem Klassentyp auf nicht-generische Weise durchführt. Dies ist der Ansatz, den der Allocator-Wrapper verfolgt

  • Die Klassen BlittableAllocator, GenericAllocator und SpanByteAllocator sind jetzt die Wrapper-Strukturen mit Key, Value und TStoreFunctions Typparametern. Diese implementieren die IAllocator-Schnittstelle.
  • Es gibt neue Klassen BlittableAllocatorImpl, GenericAllocatorImpl und SpanByteAllocatorImpl, die den Großteil der Funktionalität wie zuvor implementieren, einschließlich der Ableitung von AllocatorBase. Diese haben ebenfalls Key, Value und TStoreFunctions Typparameter; TAllocator wird nicht als Typparameter benötigt, da bekannt ist, dass es sich um die XxxAllocator Wrapper-Struktur handelt. Die Wrapper-Strukturen enthalten eine Instanz der Klasse XxxAllocatorImpl.
  • AllocatorBase selbst enthält nun ein _wrapper-Feld, das eine Struktur-Wrapper-Instanz ist (die den Instanzzeiger der vollständig abgeleiteten Allocator-Klasse enthält) und auf die IAllocator-Schnittstelle beschränkt ist. AllocatorBase selbst ist auf TStoreFunctions und TAllocator templatiert.

Die neue Allocator-Definition unterstützt zwei Schnittstellen

  • IAllocatorCallbacks, die von IAllocator abgeleitet wird. Diese enthält die abgeleiteten Allocator-Methoden, die von AllocatorBase aufgerufen werden und die wir inline haben möchten anstelle von virtcall. Die Struktur-Wrapper AllocatorBase._wrapper implementiert IAllocatorCallbacks, sodass der Aufruf von _wrapper den Aufruf an IAllocatorCallbacks inline umleitet, der dann die Implementierung der abgeleiteten *AllocatorImpl-Klasse aufruft.
  • IAllocator : IAllocatorCallbacks. Dies sind alles inline aufgerufene Aufrufe auf dem Allocator, einschließlich IAllocatorCallbacks.
    • Es stellt sich heraus, dass IAllocatorCalbacks nicht als separater Typparameter beibehalten werden kann, da IAllocator weitergegeben werden muss, aber IAllocatorCallbacks als separate Schnittstelle (anstatt alles in IAllocator zu integrieren) bestehen bleibt, da die Organisation nützlich sein kann.

Es gibt immer noch eine Reihe von abstrakten AllocatorBase-Methoden, für die das Inlining des Methodenaufrufs aufgrund der Zeit für den Gesamtaufruf nicht wichtig ist. Dies sind hauptsächlich I/O- und Scan/Iterationsmethoden.

Innerhalb von TsavoriteKV haben wir

  • hlog bleibt, ist aber jetzt vom Typ TAllocator (die Wrapper-Struktur).
  • hlogBase ist neu; es ist die AllocatorBase. Alle Aufrufe auf dem Allocator, die wir nicht inlinen müssen (oder nicht virtuell sind, wie *Address), werden jetzt über hlogBase aufgerufen.
    • Es wäre sauberer, diese in allocator und allocatorBase umzubenennen.

Es gibt auch eine neue Klasse AllocatorSettings, die von TsavoriteKV beim Instanziieren des Allocators erstellt wird. Die Allocator-Instanziierung erfolgt durch eine Factory Func<AllocatorSettings, TStoreFunctions> anstatt durch Übergabe als Objekt, da

  • Der Aufrufer müsste mehr interne Details wie die Epoch, ob ein ReadCache erstellt werden soll usw. kennen.
  • Wir erstellen temporäre TsavoriteKVs, z. B. beim Scannen oder Kompaktieren, sodass es keine Möglichkeit gibt, diese Instanzen zu übergeben.