CuckooFilter结构体
typedef uint8_t CuckooFingerprint;//8位置指纹
typedef uint64_t CuckooHash;//64为的哈希值
typedef uint8_t CuckooBucket[1];//定义了一个CuckooBucket类型,他是一个具有1个元素的uint8_t数组类型,每个桶都是1个字节,这个==uint8_t*,
typedef uint8_t MyCuckooBucket;
CuckooFilter(相当于一个链表每个节点元素都是一个CuckooFilter过滤器(定义为SubCF))
typedef struct {
//numbucket*bucketsize=最多能添加的元素个数
uint64_t numBuckets;//表示哈希表中桶的个数,这个是一个基数,相当于最低的位置的节点的,后面节点可能会更多
uint64_t numItems;//哈希表中元素的个数
uint64_t numDeletes;//删除元素的个数,当删除的元素达到一定量的时候,需要进行一个元素的压缩,将槽位迁移,提高有效性
uint16_t numFilters;//节点的个数
uint16_t bucketSize;//每个桶里面能容纳的元素个数
uint16_t maxIterations;//在插入元素的时候,最多能迭代的次数,超过这个次数就插入失败
//我们可以平衡内存和性能,确保Cuckoo Filter具有足够的存储空间来存储数据,并且尽可能地减少内存浪费。
uint16_t expansion;//在哈希表需要扩容的时候,需要扩容的比例因子,newbucket=numbucket*(expansion^nfilter),保证按比例增大
SubCF *filters;//链表,每一个元素是一个cuckoo filter
} CuckooFilter;
SubCF(每一个cuckoofilter子节点)
typedef struct {
uint64_t numBuckets : 56;//哈希表中的桶个数
uint64_t bucketSize : 8;//每个哈希表能容纳的元素个数
MyCuckooBucket *data;//一个数组,每个元素是一个uint8 a[];//相当于这样的一个数组,每个哈希数组中存储最大不超过8位的指纹,存储的元素个数就是numbucket*bucketsize
} SubCF;
Cuckookey(每一个value都有两个索引位置和一个8位指纹)
typedef struct {
uint64_t i1;
uint64_t i2;
CuckooFingerprint fp;
} CuckooKey;
插入
CF.ADD key value
//伪代码
hash=gethash(value)
insert(hash)
{
Cuckookey pararm=getpararm(hash);//根据这个hash获得对应的指纹和槽位
for i=CF.numfilter-1;i>=0;i--// 从最后一个节点开始
if(在当前节点的某个槽位上找到了元素)
把pararm->fp插入到该槽位上
return ok
//没有找到,就需要进行驱逐替换
count=0;
bucketidx=pararm->h1%numbucket;//获得该节点的其中一个bucketidx
while(count++<maxevit)//如果超过了最大的驱逐数,就直接返回
将当前节点插入到该bucketidx的两个槽位的其中一个槽位上
获得被驱逐元素的另一个bucketidx位置
如果当前bucketidx有空位可以插入就插入,结束循环 return ok
该bucketidx的位置都被占了,就从当前的bucketidx中选一个进行替换,循环
//都无法插入,就需要进行替换策略
count=0
while count++<maxevit
{
//随机挑选一个桶进行替换,调整该cuckoo filter元素分布情况
}
//当前没有元素
grow();//增加一个新的cuckoo filter节点
return insert//增加完节点后继续递归执行插入的逻辑
}
元素替换的逻辑
完整代码
static CuckooInsertStatus Filter_KOInsert(CuckooFilter *filter, SubCF *curFilter,
const LookupParams *params) {
uint16_t maxIterations = filter->maxIterations;
uint32_t numBuckets = curFilter->numBuckets;
uint16_t bucketSize = filter->bucketSize;
CuckooFingerprint fp = params->fp;//获得指纹,现在要插入元素的指纹
uint16_t counter = 0;//执行替换的次数
uint32_t victimIx = 0;//被替换的指纹索引
uint32_t ii = params->h1 % numBuckets;//ii计算索引位
while (counter++ < maxIterations) {
uint8_t *bucket = &curFilter->data[ii * bucketSize];//操纵真正的桶的元素
swapFPs(bucket + victimIx, &fp);//通过交换操作,就把fp插入进去了,而不需要进行赋值,
//较换之后,将原来这个位置的值放到
ii = getAltHash(fp, ii) % numBuckets;//计算另一个哈希位置
// Insert the new item in potentially the same bucket
uint8_t *empty = Bucket_FindAvailable(&curFilter->data[ii * bucketSize], bucketSize);//
if (empty) {
//如果当前元素有空槽位
// printf("Found slot. Bucket[%lu], Pos=%lu\n", ii, empty - curFilter[ii]);
// printf("Old FP Value: %d\n", *empty);
// printf("Setting FP: %p\n", empty);
*empty = fp;//把这个fp插入到他另一个哈希位置
return CuckooInsert_Inserted;
}
//没有空位置的话,我们就强制在这个桶里面选择一个元素进行交换,victix+1这样就保证不会相同
victimIx = (victimIx + 1) % bucketSize;//ictimtx表示当前元素被添加到桶中的位置,如果刚才这个位置的第一个位置大概率已经被交换过了,+1,保证了下次要交换的时候
}
// If we weren't able to insert, we roll back and try to insert new element in new filter,无法插入,就需要执行替换策略,将当前桶的第
counter = 0;
//随机挑选一个桶进行替换,防止过多的元素插入到同一个桶里面
while (counter++ < maxIterations) {
victimIx = (victimIx + bucketSize - 1) % bucketSize;
ii = getAltHash(fp, ii) % numBuckets;
uint8_t *bucket = &curFilter->data[ii * bucketSize];
swapFPs(bucket + victimIx, &fp);//随机挑选一个桶来进行替换
}
return CuckooInsert_NoSpace;
}
static CuckooInsertStatus CuckooFilter_InsertFP(CuckooFilter *filter, const LookupParams *params) {
for (uint16_t ii = filter->numFilters; ii > 0; --ii) {
//从最后一个节点开始往前找
uint8_t *slot = Filter_FindAvailable(&filter->filters[ii - 1], params);//计算可用的槽位
if (slot) {
*slot = params->fp;//把对应的fp插入进去
filter->numItems++;//该元素的元素个数增加
return CuckooInsert_Inserted;//有空槽位,就直接返回了
}
}
//找了所有节点都没有位置
// No space. Time to evict!,执行驱逐操作
CuckooInsertStatus status =
Filter_KOInsert(filter, &filter->filters[filter->numFilters - 1], params);//从最后一个链节点开始操作
if (status == CuckooInsert_Inserted) {
//插入成功的状态,
filter->numItems++;//怎加当前元素的个数
return CuckooInsert_Inserted;
}
//插入失败,不存在空余槽位
if (filter->expansion == 0) {
return CuckooInsert_NoSpace;//expansion=0说明不允许继续插入
}
//当前节点已经满了不允许继续插入了,同时允许我们进行扩容,我们就需要进行扩容,到新的节点上面操作
if (CuckooFilter_Grow(filter) != 0) {
return CuckooInsert_MemAllocFailed;
}
// Try to insert the filter again
return CuckooFilter_InsertFP(filter, params);//再执行一次
}
//hash就是需要插入value处理过的hash值
CuckooInsertStatus CuckooFilter_Insert(CuckooFilter *filter, CuckooHash hash) {
LookupParams params;
getLookupParams(hash, ¶ms);//根据该key对应的哈希值,生成他对应的两个哈希值,以及指纹
return CuckooFilter_InsertFP(filter, ¶ms);
}
删除
hash=gethash(value)
delete(hash)
Cuckookey=getparam(hash)
for i=CF.numfilter-1;i>=0;i--
if 发现该元素存在
将该槽位的元素置空
CF.numdelete--;
if CF.numdelete 超过了总容量的一定比例
compact()进行压缩,槽位迁移
//删除过滤器中的元素
int CuckooFilter_Delete(CuckooFilter *filter, CuckooHash hash) {
LookupParams params;
getLookupParams(hash, ¶ms);
for (uint16_t ii = filter->numFilters; ii > 0; --ii) {
//在当前的节点上进行删除这个元素
if (Filter_Delete(&filter->filters[ii - 1], ¶ms)) {
//删除成功
filter->numItems--;//元素总个数减少
filter->numDeletes++;//删除的个数增加
//如果删除元素超过了元素总数的10%,就需要进行合并,因为有很多空间被闲置了
if (filter->numFilters > 1 && filter->numDeletes > (double)filter->numItems * 0.10) {
CuckooFilter_Compact(filter, false);
}
return 1;
}
}
return 0;
}
压缩
#define RELOC_EMPTY 0
#define RELOC_OK 1
#define RELOC_FAIL -1
/**
* Attempt to move a slot from one bucket to another filter
*/
static int relocateSlot(CuckooFilter *cf, CuckooBucket bucket, uint16_t filterIx, uint64_t bucketIx,
uint16_t slotIx) {
LookupParams params = {
0};//生成一个新的param插入到前面的节点中
if ((params.fp = bucket[slotIx]) == CUCKOO_NULLFP) {
//zhesdd
// Nothing in this slot.
return RELOC_EMPTY;//为空的话,这个槽位就不需要进行迁移
}
// Because We try to insert in sub filter with less or equal number of
// buckets, our current fingerprint is sufficient
//把当前槽位的元素平行迁移到前一个节点
params.h1 = bucketIx;
params.h2 = getAltHash(params.fp, bucketIx);
// Look at all the prior filters and attempt to find a home
for (uint16_t ii = 0; ii < filterIx; ++ii) {
uint8_t *slot = Filter_FindAvailable(&cf->filters[ii], ¶ms);//在每一个节点上看,是否有这两个位置是空闲的,那么就插入上去
if (slot) {
*slot = params.fp;//把之前的元素进行设置
bucket[slotIx] = CUCKOO_NULLFP;//把之前的槽设置成空
return RELOC_OK;//设置完当前槽就迁移完成了
}
}
return RELOC_FAIL;//前面节点已经没有任何过滤器可以进行使用插入改slot了,那么就返回fatal
}
/**
* Attempt to strip a single filter moving it down a slot
*/
//将当前节点的有用词槽位迁移到前面的节点
static uint64_t CuckooFilter_CompactSingle(CuckooFilter *cf, uint16_t filterIx) {
SubCF *currentFilter = &cf->filters[filterIx];//获得当前的节点
MyCuckooBucket *filter = currentFilter->data;//获得对应的哈希表
int rv = RELOC_OK;
//这个是在访问最后一个节点
for (uint64_t bucketIx = 0; bucketIx < currentFilter->numBuckets; ++bucketIx) {
//遍历当前哈希表中存储的所有元素
for (uint16_t slotIx = 0; slotIx < currentFilter->bucketSize; ++slotIx) {
int status = relocateSlot(cf, &filter[bucketIx * currentFilter->bucketSize], filterIx,
bucketIx, slotIx);
if (status == RELOC_FAIL) {
//改节点的元素,往前迁移都被插满了,就无法再进行插入了
rv = RELOC_FAIL;
}
}
}
// we free a filter only if it the latest one
// 如果该节点全部都迁移完了,就需要将最后一个节点进行释放
if (rv == RELOC_OK && filterIx == cf->numFilters - 1) {
CUCKOO_FREE(filter);
cf->numFilters--;
}
return rv;
}
/**
* Attempt to move elements to older filters. If latest filter is emptied, it is freed.
* `bool` determines whether to continue iteration on other filters once a filter cannot
* be freed and therefore following filter cannot be freed either.
*/
//重新组织元素,减少内存空间
void CuckooFilter_Compact(CuckooFilter *cf, bool cont) {
for (uint64_t ii = cf->numFilters; ii > 1; --ii) {
//遍历所有的过滤器,将其中的元素重刑分配到更老的filter中,如何成功,当前的就需要被释放掉,
if (CuckooFilter_CompactSingle(cf, ii - 1) == RELOC_FAIL && !cont) {
//一直持续,直到没有槽位可以操作为止
// if compacting failed, stop as lower filters cannot be freed.
break;
}
}
cf->numDeletes = 0;//把这个值设置成0
}