CMU15445-project2-满分收获总结


在这里插入图片描述
(我也不知道排名为啥这么高,完全没有优化)
在这里插入图片描述


TASK #1 - PAGE LAYOUTS
1.1 对于两种page的理解

bucket_page:用来存储键值对的page,利用readable_[]判断当前index是否存有数据,occupied判断当前index是否装过数据(按道理这玩意儿没啥用)。而数组就用来存储键值对。
directory_page:其实可以看作是HashTable的信息存储地,因为需要并发控制以及保存关键信息,故直接利用一个page来存储这些信息,每次使用的时候提取该page出来就行。

1.2 一些简单的优化思路
  1. 插入的时候,需要先判断是否有空位,然后再找位置插入。可以在判断有没有空位的时候就找好插入位置。
  2. Isfull(),Isempty()这种,可以按char直接判断是否全1或者全0,而不用一位一位遍历。

TASK #2 - HASH TABLE IMPLEMENTATION
2.1 坑:关于需要实现的文件

这里没有同以往的 TASK文档一样,明确告知我需要实现的文件在哪里,需要从最后面的测试需要提交的文件列表里面去找,但文档出现了如下错误:

图中所示的extendible_probe_hash_table并不存在,再查看后面的打包命令行可以发现,实际上的文件名是extendible_hash_table
在这里插入图片描述

2.2 如何下手(理解Task2需求)

1- extendible_hash_table的概念:本身不会存储什么pointer,depth等数据,这些数据都是需要存储在能存进硬盘的Page中的(directory_page或者bucket_page),可以把它看作一个工具类,其需要的每一个信息都是要从buffer_pool_manager中请求得到的page中获取。

2- 构造函数:需要在这里新建一个directory_page和对应的bucket_Page,初始化global_depth = 1 和`local_depth = 1``(local_depth = 0也可以,等于1时需要新建两个bucket_page,0时只需一个),新建的方法我是在测试函数中学会的:

  • 不管是 New()还是Fetch()都需要记得UnpinPage()
  • bucket_page不需要reinterpret_cast,因为新建的时候没有需要设置的参数。
  • 不需要存储长度信息,2 ^ global_depth就是长度
  • 除了directory_page_id,不要声明任何关于hashtable的私有变量来存储其信息,因为需要满足并发,每次的信息必须从bplm中调取出来才能保证安全。
  • (这里的代码不完全,不要直接复制)
auto directory_page =
      reinterpret_cast<HashTableDirectoryPage *>(buffer_pool_manager->NewPage(&directory_page_id_, nullptr)->GetData());

directory_page->IncrGlobalDepth();

page_id_t bucket_page_id_1 = INVALID_PAGE_ID;
buffer_pool_manager->NewPage(&bucket_page_id_1, nullptr)->GetData();
directory_page->SetLocalDepth(0, 1);
directory_page->SetBucketPageId(0, bucket_page_id_1);

buffer_pool_manager->UnpinPage(directory_page_id_, true);
buffer_pool_manager->UnpinPage(bucket_page_id_1, true);

其他函数:每次的逻辑大概是:根据directory_page_id_buffer_pool_manager中调取directory_page,并根据需求调取或者新建/删除bucket_page,修改完信息后,每次都记得UnpinPage()就行。

2.3 SplitInsert(),merge()

SplitInsert()

  • 先正常Insert(),在插入之前检查是否满,满了就跳转到SplitInsert()把插入工作全权交给它,没满就直接正常插入并返回。
  • 注意一种特殊情况:Split之后,仍然有bucket_page是满的,这时候向其要插入元素需要继续Split。
if (global_depth == local_depth) { // 需要增加global_depth
	direct_page->global_depth++;
	更新 direct_page->local_depths_[];
} 

// 增加local_depth
new new_bucket_page;
old_bucket_page 元素转移-> new_bucket_page;
更新 direct_page->local_depths_[];
更新 direct_page->bucket_page_ids_[];

// 递归判断是否还需要分页
bucket_page = 现在key对应的bucket_paeg
if (bucket_page->Isfull()) {
	SplitInsert();
}

Merge()

  • 特殊情况1:当前页面对应的兄弟页面(当时分裂时产生的另一个页面)已经又分裂了,也就是local_depth不相等,这时候直接放弃合并。
  • 特殊情况2:合并完成后,检查兄弟页面是否是空的,如果是空的,主动再进行合并(这种情况就是在解决特殊情况1留下来的空页面)
2.4 Debug 辅助函数

为了方便检测SplitInsert 和 Merge 是否正常工作,我给ExtendibleHashTable写了一个内置的打印函数,打印出hashtable。

template <typename KeyType, typename ValueType, typename KeyComparator>
void HASH_TABLE_TYPE::PrintTable() {
  // prinf
  auto directory_page = FetchDirectoryPage();
  uint64_t table_size = (1 << directory_page->GetGlobalDepth());
  std::cout << "-----------HASH_TABLE_MESSAGE-----------\n";
  std::cout << "global_depth: " << directory_page->GetGlobalDepth() << "\n";
  for (uint32_t i = 0; i < table_size; i++) {
    uint32_t bucket_page_id = directory_page->GetBucketPageId(i);
    uint32_t local_depth = directory_page->GetLocalDepth(i);
    auto bucket_page = reinterpret_cast<HashTableBucketPage<KeyType, ValueType, KeyComparator> *>(
        buffer_pool_manager_->FetchPage(bucket_page_id)->GetData());
    uint32_t num_readable = bucket_page->NumReadable();
    std::cout << "index: " << i << "|"
              << "bkt_id: " << bucket_page_id << "|"
              << "num_readable/size: " << num_readable << "/" << BUCKET_ARRAY_SIZE << "|"
              << "local_depth: " << local_depth << "\n";
    buffer_pool_manager_->UnpinPage(bucket_page_id, false);
  }
  buffer_pool_manager_->UnpinPage(directory_page_id_, false);
}

TASK #3 - CONCURRENCY CONTROL
3.1 bucket_page锁的使用

官方文档用法:

auto bucket_page = reinterpret_cast<HashTableBucketPage<KeyType, ValueType, KeyComparator> *>(buffer_pool_manager_->FetchPage(old_bucket_page_id)->GetData());
auto page = reinterpret_cast<Page *> (bucket_page);
page->RLatch();
page->RUlatch();

我没有理解到的是,如果把整个bucket_page转译成另一个Page,那么使用锁的时候不会改变bucket_page原有的数据项吗。
而且buffer_pool_manager_->FetchPage()返回的就是一个Page,那为何不直接使用这个返回的Page自带的锁:

auto page = buffer_pool_manager_->FetchPage(old_bucket_page_id);
auto bucket_page = reinterpret_cast<HashTableBucketPage<KeyType, ValueType, KeyComparator> *> (page->GetData());
page->RLatch();
page->RUlatch();
3.2 Unlatch 和 Unpin 的时机

如果要保证线程安全,应该在Unpin之后再使用Unlatch。

buffer_pool_manager_->UnpinPage(bucket_page_id, false);
page->RUnlatch();
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值