CMU15-445 Project2
Part I PAGE LAYOUTS
整个Hash Table分为directory和bucket。我们选择把directory和bucket都放在page里面。
设计逻辑
directory和bucket存的是不同的信息,但都是page。因为是page,所以对空间的要求比较高。需要计算好每个成员变量会占用的空间大小!不要动态分配内存。如果动态分配了内存,我们是把内存分在了堆上,那么当我们要将一个page写回到磁盘上时,并不会将堆上的信息写回,而是只写回这个page类中的成员信息。因此如果要把数据保存在page中,就要保存数据的实体,用一个定长数组。空闲需要提前计算好。
对于所有的操作,用最暴力的方法来实现,因为一个page也就4kb,直接枚举花不了多少时间。
Directory Page
Variable Name | Size | Description |
---|---|---|
page_id_ |
4 bytes | Self Page Id |
lsn_ |
4 bytes | Log sequence number (Used in Project 4) |
global_depth_ |
4 bytes | Global depth of the directory |
local_depths_ |
512 bytes | Array of local depths for each bucket (uint8) |
bucket_page_ids_ |
2048 bytes | Array of bucket page_id_t |
一些小功能实现起来非常简单。
Bucket Page
这个类因为要存数据,而且要存的数据类型是未知的,所以定义成员变量要格外小心。一个occupied_数组用来标注每一位有没有被占用,一个readable_数组用来标注每一位的值能不能被读取。array数组存实际的数据。因为一个page中的空间非常重要,因此这里的readable和occupied数组都用char类型,即一个字节可以表示8位的数据,用位运算就可以得到第i位的值。
需要注意的是这里的array千万不能用动态分配内存扩容的方式,因为动态内存不会被写到磁盘上。这里用了一个trick:我们将array数组定义为0长度的,将这个成员变量放在类的最后面,就表示这个成员使用的空间一直到类的空间的末尾。除此之外,千万不能定义其他的成员变量!不然就会和前面的计算结果冲突。
函数设计
GetValue
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
Insert
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
如果是要插入一个pair,最好是先用make_pair这个函数来初始化一个!
Remove
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
如果删除了一个元素,为了不移动整个hash table,我们直接在这个位置上设一个墓碑。这个位置以后不能写入,也不能读出来了。不用真的把元素给删掉。
Part II Hash Table Implementation
设计逻辑
先把单线程的版本写好,再来加锁,最后优化锁(尽量让锁细一点)。
这里就涉及到和低层的交互了,会用到BufferPool来获取page。在写每个函数之前都先确认一遍和低层的交互是否有问题。比如这里我们创建了一个Page,必须先用reinterpret_cast来进行类型转换,转换为我们实现的page类型,不管是NewPage还是FetchPage最后都必须要UnpinPage,而且在DeletePage之前也必须要先UnpinPage。
在写每个函数前,先列个清单,想好这个函数的每一步要做什么。如果设计到helper function,就要先写好helper function。
搞清楚bucket_index和bucket_id的区别。索引和id的区别!
函数设计
构造函数
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
这里要注意的点就是这3个page一定要Unpin掉。
GetValue
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
Insert
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
先把一开始的框架写好,即取directory_page和bucket_page,把必要的信息都用变量给保存起来。然后就是一些常规操作,比如是否要分裂,分裂后目录的改变:先复制原来一半的指针,然后更新要指向新的bucket_page的指针。
Remove
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
这里要注意合并的几个条件。合并其实也就是改指针。
UpdateDirectoryPage
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
SplitBucketPage
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
CheckMerge
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
Merge
1 | template <typename KeyType, typename ValueType, typename KeyComparator> |
这里一定要先unpin掉page然后才能delete。