2023fall cmu15445,project2 extendible hash index

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转换为右值引用

  1. 首先这个task的关键是移动构造函数:**在移动构造函数中,我们将other对象的资源指针移动到当前对象中,并将other对象的资源指针设置为nullptr,以避免重复释放资源。在main函数中,我们使用std::move将obj1转换为右值引用,从而调用移动构造函数。**还有就是重载运算符;

task02 extendible hash table pages

  1. Hash Table Header Page:包含directory_page_ids_max_depth_;标题页位于基于磁盘的可扩展哈希表的第一级,哈希表只有一个标题页。它存储指向目录页的逻辑子指针(as page ids)。您可以将其视为静态的一级目录页面。
  2. Hash Table Directory Page:目录页位于基于磁盘的可扩展哈希表的第二级。它们中的每一个都存储指向存储桶页面的逻辑子指针(as page ids),以及用于处理存储桶映射和动态目录增长和收缩的元数据。bucket_page_ids_
  3. 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)是两个重要的概念。
  1. 全局深度(global depth):全局深度是可拓展哈希表中用于划分桶(bucket)的指标。具体来说,全局深度表示哈希表中桶的数量,也可以理解为哈希表的总容量。全局深度以二进制形式表示,并决定了哈希表中桶的数量。如果全局深度为k,那么哈希表就有2^k个桶。一个key的二进制是10111,global depth是3,返回111;
  2. 局部深度(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:这个简直非常痛苦,想写这个首先要完成InsertToNewDirectoryInsertToNewBucket,除此之外还需要完成当bucketpage满了的时候的分裂操作,我的做法是新写了一个SplictInsertBucket函数,下面分别介绍;

    1. InsertToNewDirectory:这个算是比较容易的,只需要在构造函数新建page的那个写法的基础上加header->SetDirectoryPageId(directory_idx, page_id);
    2. InsertToNewBucket:这个在上面的基础上加了一个bucket_page->Insert(key, value, cmp_)的操作,然后bucket_guard.Drop(), return就行了
    3. 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然后卡死锁了的情况;
  • 不过其他测试估计还是要改
  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
CMU 15445 课程的 Project 0 是一个调试练习,旨在帮助学生熟悉调试工具和技术。在这个项目中,你将开始使用 GDB 和 Valgrind 这两个常用的调试工具,以及一些其他辅助工具。以下是一些问题和步骤,帮助你完成这个练习: 1. 你需要查看项目中提供的代码,并了解它的结构和功能,这样你才能更好地理解程序的逻辑和可能出现的 bug。 2. 接下来,你需要编译项目,并确保没有编译错误。如果出现错误,你需要修复它们,这可能需要检查一些语法错误或缺失的库。 3. 一旦成功编译项目,你就可以使用 GDB 进行调试了。GDB 是一个强大的调试器,可以帮助你找出程序中的错误。你可以使用 GDB 来单步执行代码、设置断点、查看变量的值等等。通过使用 GDB,你可以逐步查看代码运行的路径,并找出程序崩溃或产生错误的原因。 4. 在使用 GDB 进行调试时,你可以通过设置断点来暂停程序的执行,并查看变量的值和程序的状态。你可以使用“break”命令在程序中设置断点,并通过“run”命令启动程序。当程序到达这个断点时,它会停止执行,你可以使用“print”命令查看变量的值,或者“step”命令逐步执行代码。 5. 另一个常用的调试工具是 Valgrind。Valgrind 可以帮助你检测内存泄漏和错误的访问方式。你可以使用“valgrind”命令来运行程序,并查看 Valgrind 的输出。它会告诉你有关程序中任何潜在问题的信息,例如未初始化的变量、访问越界等。 6. 最后,当你发现 bug 并修复它们后,可以运行各种测试用例来验证程序的正确性。测试用例可以帮助你确定程序是否按预期工作,并且在修改代码后,它们可以帮助你确保你的修复没有引入新的错误。 通过完成 CMU 15445 项目 0 的调试练习,你将掌握一些重要的调试技巧和工具,这对于进一步开发和调试软件应用程序将非常有用。希望上述步骤和建议对你有所帮助,祝你顺利完成这个项目!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值