project2 extendible hash index
2023fall cmu15445,完全新手向
task01 read/write page guards
class BasicPageGuard{
//...有这几个成员
BufferPoolManager *bpm_{nullptr};
Page *page_{nullptr};
bool is_dirty_{false};
}
MyClass(MyClass&& other) : resource(other.resource) {
other.resource = nullptr;
};
MyClass obj1;
MyClass obj2(std::move(obj1)); // 使用std::move将obj1转换为右值引用
- 首先这个task的关键是移动构造函数:**在移动构造函数中,我们将other对象的资源指针移动到当前对象中,并将other对象的资源指针设置为nullptr,以避免重复释放资源。在main函数中,我们使用std::move将obj1转换为右值引用,从而调用移动构造函数。**还有就是重载运算符;
task02 extendible hash table pages
Hash Table Header Page
:包含directory_page_ids_
和max_depth_
;标题页位于基于磁盘的可扩展哈希表的第一级,哈希表只有一个标题页。它存储指向目录页的逻辑子指针(as page ids)。您可以将其视为静态的一级目录页面。Hash Table Directory Page
:目录页位于基于磁盘的可扩展哈希表的第二级。它们中的每一个都存储指向存储桶页面的逻辑子指针(as page ids),以及用于处理存储桶映射和动态目录增长和收缩的元数据。bucket_page_ids_
Hash Table Bucket Page
:存储桶页面位于基于磁盘的可扩展哈希表的第三级。它们是实际存储键值对的那些。
- 首先写
bucket_page
:因为test的第一个是bucket page;bucketpage写的同时还要写bufferpoolmanager中的几个没写的成员函数;首先是Remove函数,要调用RemoveAt函数,利用移动构造函数,维护size_和桶里成员都在前size_个块中;Lookup函数,这个函数的意思是,找key一样的然后把参数传给value那这个的@return ture if the key are present, false if not found
不就可以了…因为这个没理解对,改了很久,所以在这吐槽一下.
for (auto i = bucket_idx; i < size_ - 1; i++) {
array_[i] = std::move(array_[i + 1]);
}
header_page
:主要是实现HashToDirectoryIndex(uint32_t hash)
这个函数,看测试用例可以知道,这个是取开头的max_depth_个二进制数所以直接hash >> (sizefo(hash)*8 - max_depth_)
还有理解MaxSize和max_depth_的关系,就是2^max_depth_,因为深度指的是二进制的位数;
最关键的是这个
- directory page:
DirectoryIndex = Hash(key) & GLOBAL_DEPTH_MASK
;global_depth_(The key size that maps the directory)和local_depth_(The key size that has previously mapped the bucket)是什么意思 - 在可拓展哈希表中,全局深度(global depth)和局部深度(local depth)是两个重要的概念。
- 全局深度(global depth):全局深度是可拓展哈希表中用于划分桶(bucket)的指标。具体来说,全局深度表示哈希表中桶的数量,也可以理解为哈希表的总容量。全局深度以二进制形式表示,并决定了哈希表中桶的数量。如果全局深度为k,那么哈希表就有2^k个桶。一个key的二进制是10111,global depth是3,返回111;
- 局部深度(local depth):局部深度是可拓展哈希表中每个桶的指标。每个桶都有一个对应的局部深度值,表示桶的深度或者说桶内存储的元素数量。局部深度以二进制形式表示,并决定了哈希表中每个桶的容量。n,每个元素key的低n位是相同的;
-
size_的大小其实就是2^GD,全局深度一定小于等于局部深度,如果全局深度都小于所有的局部深度说明可以Shrink;
还有一个需要注意的地方:VerifyIntegrity()和PrintDirectory()也不知道是在哪实现的,提供了一个在gdb测试的时候,可以检查和打印出目录的函数,非常方便
; -
GetGlobalDepthMask()
:取全局深度的mask(1<<global_depth_)-1
比如GD是2,mask就是二进制的11; -
HashToBucketIndex(uint32_t hash)
:hash & GetGlobalDepthMask()
-
GetSplitImageIndex(uint32_t bucket_idx)
:这个函数的意思写的很模糊,看了很多blog最后明白了,在设置LD的时候,要进行分裂操作,分裂的时候会产生两个桶,要分别设置这两个的localDepth,所以就需要一个从分裂的桶索引到另一个分裂的桶的函数;具体的方法就是:LD == 2, 101和001这两个id, 使得GetSplitImageIndex(1001
) == 5101
;就是和100(1<<LD)
做位或操作;这里的细节还需要自己好好debug,我觉得我写的也有很大问题,他这个不能确保每次LD都只增加1…如果用错了就全错了总之; -
IncrGlobalDepth()
:非常重要,增加GD,需要注意的情况是如果增加GD之后,LD < GD
就需要给新添加的空桶加上索引,就需要一个和GetSplit类似的方法;
auto ans = (1 << global_depth_);
local_depths_[i] = local_depths_[i ^ ans];
bucket_page_ids_[i] = bucket_page_ids_[i ^ ans];
CanShrink()
:如果所有的GD都大于LD就可以Shrink;
task03 extendible hashing implementation
经过整整两天的看资料,参考test代码和网上找到的一些资料,debug了一天终于通过了三个本地测试;这个任务真的是相当的繁琐,代码也是第一次写的时候,make出的各种错误,在这里一起总结一下;
DiskExtendibleHashTable的构造函数
:在这里要创建出header_page主要就是参考test中的方法:
BasicPageGuard header_guard = bpm_->NewPageGuarded(&header_page_id_);
auto header_page = header_guard.AsMut<ExtendibleHTableHeaderPage>();
header_page->Init(header_max_depth_);
header_guard.Drop();
-
GetValue
:这个相对简单,首先如果获取的page_id==INVALID_PAGE_ID
就直接return false
,所以这里要注意directory和bucket的page都要初始化为INVALID_PAGE_ID
,还有一个重要的directory它获取page_id的时候的返回的是uint32_t类型如果没记错,我直接改成page_id_t了,希望后面没问题,需要用到bucketpage->Lookup(key, value, cmp_)
这个方法; -
Insert
:这个简直非常痛苦,想写这个首先要完成InsertToNewDirectory
和InsertToNewBucket
,除此之外还需要完成当bucketpage满了的时候的分裂操作,我的做法是新写了一个SplictInsertBucket
函数,下面分别介绍;InsertToNewDirectory
:这个算是比较容易的,只需要在构造函数新建page的那个写法的基础上加header->SetDirectoryPageId(directory_idx, page_id);
InsertToNewBucket
:这个在上面的基础上加了一个bucket_page->Insert(key, value, cmp_)
的操作,然后bucket_guard.Drop(), return
就行了SplictInsertBucket
:这个可以说非常繁琐,首先如果这个bucketpage的LD已经是最大的深度了,返回false,否则:
GD >= LD
所以当GD == LD
的时候先增加GD在增加LD,在增加GD或者LD之前先获取增加后的new_bucket_idx
,根据写这个函数的时候你自己的逻辑;获取原来的bucket_pid,申请新的new_bucket_pid,为新的new_bucket_pid在directory中设置索引- 遍历原来的bucket_page,如果里面的pair应该是在新的page中,从原来的page删除,插入到新的page;
auto idx = directory->HashToBucketIndex(Hash(k)); if (idx == new_bucket_idx) { new_bucket_page->Insert(k, v, cmp_); bucket_page->Remove(k, cmp_); }
- 插入新的key,value:我的做法是用它的key重新找一次bucket_page_idx重复一下这个操作,然后进行插入,如果bucket还是满的就再调用一次SplictInsertBucket这个函数;
-
Insert
:过程是找page然后没有就新建page,新建page失败就return false;bucket满了就分裂,分裂不成功就return false -
Remove
:和Insert
比较相似,找不到就return false,找到了就调用bucket_page->Remove(key, cmp_);
task04 Concurrency Control
本来以为这个很简单,结果还是改了四五个小时才完全改好;
- 首先就是把上一个写的
BasicPageGuard
改成read或者writepageguard,怎么说呢看似简单实则一点都不难,就是非常的繁琐…总是出现没drop然后卡死锁了的情况; - 不过其他测试估计还是要改