StoreFunctions 和分配器结构包装器
本节讨论这两者,因为它们是添加两个额外类型参数 TStoreFunctions
和 TAllocator
到 TsavoriteKV
以及各种会话和 *Context
(例如 BasicContext
)的更改的一部分。两者的目的是通过内联调用提供更好的性能。StoreFunctions 还为存储级别而非会话级别的操作提供了更好的逻辑设计,如下所述。
从调用者的角度来看,我们在 TsavoriteKV<TKey, TValue, TStoreFunctions, TAllocator>
上有两个新的类型参数。TStoreFunctions
和 TAllocator
也存在于 *.Context
(例如 BasicContext
)上。C# 只允许 'using' 别名作为命名空间声明的第一行,并且该别名是文件本地的,并被后续的 'using' 别名识别,因此多个文件中的“Api”别名(例如 BasicGarnetApi
)现在变得更长。
TsavoriteKV
构造函数已更改为接受 3 个参数
KVSettings<TKey, TValue>
。这取代了之前一长串的参数。LogSettings
、ReadCacheSettings
和CheckpointSettings
已成为内部类,仅由TsavoriteKV
(从TsavoriteKVSettings
创建)在实例化分配器时使用(例如,新的AllocatorSettings
包含一个LogSettings
成员)。SerializerSettings
已被移除,取而代之的是IStoreFunctions
上的方法。TStoreFunctions
的实例。这通常通过调用静态StoreFunctions
工厂方法来创建,并传入要包含的各个组件。- 用于
TAllocator
实例化的工厂Func<>
。
这些将在下面更详细地描述。
StoreFunctions 概述
StoreFunctions
指的是位于 TsavoriteKV
级别的回调函数集,类似于会话级别的 ISessionFunctions
。类似于 ISessionFunctions
,有一个 IStoreFunctions
。然而,ISessionFunctions
的实现可以是结构体或类;Tsavorite 提供 SessionFunctionsBase
类作为实用工具,可以从中继承。但是,由类实现的类型参数不会生成内联代码。
因为 IStoreFunctions
旨在提供最大程度的内联,所以 Tsavorite 不提供 StoreFunctionsBase
。相反,Tsavorite 提供了一个 StoreFunctions
结构体实现,并传入可选的实现,用于
- 键比较(以前作为
ITsavoriteKeyComparer
接口传入,该接口未内联) - 键和值序列化器。由于类型参数的限制,这些必须作为
Func<>
传入,该Func<>
创建实现实例,并且由于序列化是昂贵的操作,我们保留IObjectSerializer<TKey>
和IObjectSerializer<TValue>
接口,而不是用键和值序列化器类型参数来混淆IStoreFunctions<TKey, TValue>
接口。 - 记录处置(以前在
ISessionFunctions
上作为多个方法,现在只有一个带有“原因”参数的方法)。 - 检查点完成回调(以前在
ISessionFunctions
上)。
当然,由于 TsavoriteKV
具有 TStoreFunctions
类型参数,因此这可以是调用者实现的任何类型,包括类实例(这会更慢)。
分配器包装器概述
与 StoreFunctions
一样,分配器包装器旨在提供最大程度的内联。如上所述,由类实现的类型参数不会生成内联代码;JITted 代码是通用的,用于单个 IntPtr
大小的引用。然而,对于结构体,JITter 会生成特定于该特定结构体类型的代码,部分原因是大小可能不同(例如,作为参数压入堆栈时)。
有一个技巧允许由类实现的类型参数内联:泛型类型必须是包装类类型并以非泛型方式调用该类类型的结构体。这是分配器包装器采用的方法
BlittableAllocator
、GenericAllocator
和SpanByteAllocator
类现在是包装结构体,带有Key
、Value
和TStoreFunctions
类型参数。这些实现了IAllocator
接口。- 有新的
BlittableAllocatorImpl
、GenericAllocatorImpl
和SpanByteAllocatorImpl
类,它们像以前一样实现了大部分功能,包括继承自AllocatorBase
。这些也具有Key
、Value
和TStoreFunctions
类型参数;TAllocator
不需要作为类型参数,因为它已知是XxxAllocator
包装结构体。包装结构体包含XxxAllocatorImpl
类的一个实例。 AllocatorBase
本身现在包含一个_wrapper
字段,它是一个结构体包装器实例(包含完全派生的分配器类的实例指针),该实例受IAllocator
接口的约束。AllocatorBase
本身是根据TStoreFunctions
和TAllocator
模板化的。
新的分配器定义支持两个接口
IAllocatorCallbacks
,由IAllocator
继承。这包含由AllocatorBase
调用的派生分配器方法,我们希望将其内联而不是虚调用。结构体包装器AllocatorBase._wrapper
实现了IAllocatorCallbacks
,因此对_wrapper
的调用内联了对IAllocatorCallbacks
的调用,然后该调用会进一步调用到派生的*AllocatorImpl
类实现。IAllocator : IAllocatorCallbacks
。这是分配器上的所有内联调用,包括IAllocatorCallbacks
。- 事实证明,不可能将
IAllocatorCallbacks
作为一个单独的类型参数保留,因为IAllocator
必须传播,但IAllocatorCallbacks
仍然是一个单独的接口(而不是将其全部组合到IAllocator
中),因为这种组织可能很有用。
- 事实证明,不可能将
仍然有许多抽象的 AllocatorBase
方法,由于整个调用的时间,方法的内联并不重要。这些主要是 IO 和扫描/迭代方法。
在 TsavoriteKV
中,我们有
hlog
仍然存在,但现在是TAllocator
类型(包装结构体)。hlogBase
是新的;它是AllocatorBase
。所有我们不需要内联(或不是虚拟的,例如 *Address)的分配器上的调用现在都在 hlogBase 上调用。- 将这些重命名为
allocator
和allocatorBase
可能会更清晰。
- 将这些重命名为
还有一个新的 AllocatorSettings
类,由 TsavoriteKV
在实例化分配器时创建。分配器实例化通过工厂 Func<AllocatorSettings, TStoreFunctions>
完成,而不是作为对象传入,因为
- 调用者必须知道更多内部信息,例如纪元、是否创建读取缓存等等。
- 我们创建临时的
TsavoriteKV
,例如在扫描或压缩时,因此无法传入这些实例。