2022-04-14 18:43阅读: 178评论: 0推荐: 0

简单复盘CMU15-445 fall2021 Lab Project2

概述

基于已经实现了的Buffer Pool实现一个Extendable HashTable,具体两个部分。
基本数据结构Bucket和Directory以及HashTable的主体。

Hash Table Bucket Page

基本说明

BucketPage的size和磁盘以及内存中page的大小相同,都是4KB,三个数据成员:readable_occupied_以及array_, 三者都是数组。

  • readable_:char类型数数组,因此具有sizeof(readable_) * 8 个bit,第k个bit用来记录 array[k]是否有存储的可读数据。
  • occupied_:同上,但是每个bit用来记录array[i]是否被使用过,意义不大。不过由于我们使用array_是顺序扫描,所以可在扫描array_时提前结束,具体可看该官方在该page中实现的PrintBucket方法。
  • array_:存储的是支持泛型的std::pair,而BucketPage限制了大小,因此array_采用的是GNU C支持的零长数组。
    其余的成员方法看起来难度都不大,但是想实现更高的性能,必须要使用很多巧妙的位运算,一开始我认为自己实现的方法都非常棒,但是实际测试时却有很多bug,不得已只能修改为比较平庸的算法。具体实现不放了。

注意事项

  1. 这个类的默认构造函数上被添加了delete关键字,关于这个关键字网上有很详细的说明,后续我也会学习一下贴一篇笔记,总之就是为了内存安全考虑。
  2. 注意一下三个成员变量数组长度的设置,尤其是前两个
  3. occupied_这个数组的每一个bit基本上只会被设置一次,即使对应元素被删除也不需要置0,因为我们判断位置上是否有存储的数据是根据readable_数组的bit.

Hash Table Directory Page

基本说明

同上,DirectoryPage也是对应了硬件中的一个page:数据成员如下:

  • global_depth_:当前HashTable的global depth
  • local_depths_:uint_32类型的数组,存储的是每个Bucket的local_depth
  • bucket_page_ids_:page_id_t类型的数组,存储的是当前每个Bucket指向的物理page id
  • lsn_:后面的lab中会用到的日志字段
  • page_id_:存储自己的物理page_id
  • is_valid_:应该是标识该directory是否可用,本次project中没有用到
    这个类里的方法也都比较简单,但是也涉及到一些位运算,而且注意445这里是推荐使用末尾的n位作为bucket_idx的

注意事项

  1. 虽然本身有一个page_id的字段,但是该类并不是和Buffer Pool中管理的Page对象是同一级别的,而是与Bucket Page一样,和硬件的page大小相同。
  2. 虽然使用directory管理buckets,但是directory中不存储指向page的指针,只存储bucket对应的page_id,让上层可以i使用buffer pool访问到具体的bucket page。
  3. 并不是官方提供的所有方法都必须实现并且使用

Extendble Hash Table

基本说明

这个类持有一个buffer pool管理器和一个directory_page_id,也就是说上层引擎会持有一个此类的对象,然后调用这个对象的InsertGetValueRemove方法,
讲一下逻辑,以上的三个方法,前提都是将key转换成Hash Result,然后用这个Hash Result配合当前directory的global_depth_获取到key对应的bucket_idx,有了bucket_idx之后自然也就能获取到bucket_page_id,从而也就能获取到实际的物理page。注意这个类只是持有directory_page_id_
至此,几个重要的工具方法:HashKeyToDirectoryIndexKeyToPageIdFetchDirectoryPageFetchBucketPage的作用也就显而易见了。
接下来是几个核心方法:

  1. GetValue:传入的是key引用和result数组指针,注意这里是数组,因为本次实验中要求实现的是非唯一key Hash,也就是说允许同一个key与不同的value映射,传入一个key可能会查询到多个value
  2. Insert:传入的是key和value的引用,大题的逻辑是先获取到目标bucket,上文已经讲了那几个方法的作用,拿到bucket之后检查是不是满了,如果满了就需要进行SplitInsert,也就是分裂bucket,分裂的逻辑有点复杂,中间包括directory扩容、rehash数据以及重定向bucket指针等等。后期会上参考资料。分裂完成之后再尝试重新插入目标的key和value。
  3. Remove:传入的是key和value,逻辑和Insert类似,但是反过来,如果删除成功,那么就要检查删除之后的bucket是不是为空,如果为空就要尝试着进行Merge操作,也就是合并之前SplitInsert分裂开的镜像bucket,中间掺杂了directory的收缩、重定向bucket指针,总体来说比SplitInsert要简单一些。

注意事项:

  1. 官方给出的两个Fetch方法,返回类型分别是directory page和bucket page,但是在进行并发控制时,我们需要使用Page对象中的读写锁,因此,可以将FetchBucketPage的返回类型改为Page,然后再使用reinterpret_cast,将Page->data_转型成为Bucket Page对象,这样就能使用Page对象中的读写锁保护对应的Bucket Page
  2. 类型的构造方法中需要对directory_page_id_初始化,并且为改directory page分配一个local depth为0的bucket
  3. 所有的NewPage和FetchPage操作,都会在Buffer Pool中Pin,因此当我们不再使用该page时一定要及时UnPin。读写锁也是同理。
  4. 关于并发控制,这是我第一次使用读写锁,因此没有太深的理解。最直观的想法就是,没有修改的可能,就是用读锁,只要有任何修改的可能,就要使用写锁。
  5. 另外该类中的读写锁命名为Lock,而Page中确实latch,可能值得好好思考一下。

总结

本篇文章仅作为作者自己的备忘,刚做完之后向随便记录一下加深理解,后续整个lab做完会写一份详细的解析或者是指导。当然并不会涉及具体代码,仅仅为像我一样没有什么基础的菜鸟提供一个入门的方向。

posted @   wtsclwq  阅读(178)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起