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,ReadCacheSettingsundCheckpointSettingssind zu internen Klassen geworden, die nur vonTsavoriteKV(erstellt ausTsavoriteKVSettings) beim Instanziieren der Allocators verwendet werden (z. B. hat der neueAllocatorSettingseinLogSettings-Mitglied).SerializerSettingswurde zugunsten von Methoden aufIStoreFunctionsentfernt.- Eine Instanz von
TStoreFunctions. Diese wird normalerweise durch einen Aufruf einer statischenStoreFunctions-Factory-Methode erstellt, der die einzelnen zu enthaltenden Komponenten übergeben werden. - Eine Factory
Func<>für dieTAllocator-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 SchnittstellenIObjectSerializer<TKey>undIObjectSerializer<TValue>, anstatt dieIStoreFunctions<TKey, TValue>-Schnittstelle mit den Schlüssel- und Wertserialisierer-Typparametern zu überladen. - Datensatzentsorgung (früher auf
ISessionFunctionsals 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,GenericAllocatorundSpanByteAllocatorsind jetzt die Wrapper-Strukturen mitKey,ValueundTStoreFunctionsTypparametern. Diese implementieren dieIAllocator-Schnittstelle. - Es gibt neue Klassen
BlittableAllocatorImpl,GenericAllocatorImplundSpanByteAllocatorImpl, die den Großteil der Funktionalität wie zuvor implementieren, einschließlich der Ableitung vonAllocatorBase. Diese haben ebenfallsKey,ValueundTStoreFunctionsTypparameter;TAllocatorwird nicht als Typparameter benötigt, da bekannt ist, dass es sich um dieXxxAllocatorWrapper-Struktur handelt. Die Wrapper-Strukturen enthalten eine Instanz der KlasseXxxAllocatorImpl. AllocatorBaseselbst 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 dieIAllocator-Schnittstelle beschränkt ist.AllocatorBaseselbst ist aufTStoreFunctionsundTAllocatortemplatiert.
Die neue Allocator-Definition unterstützt zwei Schnittstellen
IAllocatorCallbacks, die vonIAllocatorabgeleitet wird. Diese enthält die abgeleiteten Allocator-Methoden, die vonAllocatorBaseaufgerufen werden und die wir inline haben möchten anstelle von virtcall. Die Struktur-WrapperAllocatorBase._wrapperimplementiertIAllocatorCallbacks, sodass der Aufruf von_wrapperden Aufruf anIAllocatorCallbacksinline umleitet, der dann die Implementierung der abgeleiteten*AllocatorImpl-Klasse aufruft.IAllocator : IAllocatorCallbacks. Dies sind alles inline aufgerufene Aufrufe auf dem Allocator, einschließlichIAllocatorCallbacks.- Es stellt sich heraus, dass
IAllocatorCalbacksnicht als separater Typparameter beibehalten werden kann, daIAllocatorweitergegeben werden muss, aberIAllocatorCallbacksals separate Schnittstelle (anstatt alles inIAllocatorzu integrieren) bestehen bleibt, da die Organisation nützlich sein kann.
- Es stellt sich heraus, dass
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
hlogbleibt, ist aber jetzt vom TypTAllocator(die Wrapper-Struktur).hlogBaseist neu; es ist dieAllocatorBase. 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
allocatorundallocatorBaseumzubenennen.
- Es wäre sauberer, diese in
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.