【CMU15-445 Fall2023】Project3 Query Execution 小结

该系列博客只是为了记录自己在写Lab时的思路,按照课程要求不会在Github和博客中公开源代码。欢迎与我一起讨论交流! 这个project和之前就不一样了,开始深入数据库内核的实现了。需要理清楚一条sql语句是如何被执行的,方才能写出代码。 前置奶酪 一条SQL语句的执行 这里需要去看看一条sql语句传入bustub内部之后的代码:src/common/bustub_instance.cpp:ExecuteSqlTxn: auto BustubInstance::ExecuteSqlTxn(const std::string &sql, ResultWriter &writer, Transaction *txn, std::shared_ptr<CheckOptions> check_options) -> bool { if (!sql.empty() && sql[0] == '\\') { // 处理元命令 ... } // binder,但是在其中会使用libpg_query来解析sql语句 bustub::Binder binder(*catalog_); binder.ParseAndSave(sql); // 经过上一步后,binder中的statement_nodes_存储着所有的语句解析节点 for (auto *stmt : binder.statement_nodes_) { // 将stmt转换成BoundStatement对象,方便后面处理数据 auto statement = binder.BindStatement(stmt); // 只有不需要构建plan树、不需要进行优化的sql语句才会在switch之后继续执行 switch (statement->type_) { ... } // 生成初步的执行计划 bustub::Planner planner(*catalog_); planner.PlanQuery(*statement); // 优化刚刚的执行计划 bustub::Optimizer optimizer(*catalog_, IsForceStarterRule()); auto optimized_plan = optimizer.Optimize(planner.plan_); ....

2024-12-16 · 10 min · 1995 words · Kerolt

【CMU15-445 Fall2023】Project2 Extendible Hash Index 小结

该系列博客只是为了记录自己在写Lab时的思路,按照课程要求不会在Github和博客中公开源代码。欢迎与我一起讨论交流! 太菜了,从没打过这么艰难的仗QAQ。由于课程的要求不能公开源代码,所以网上的资源会少很多,平台上的测试案例比较全面,有的还比较刁钻,需要考虑到可拓展哈希的实现细节。在自认为写完了后,提交了近40来次总有几个测试集过不了,还好没有崩溃,在看了几篇博客的方法后,加上自己画图理解,最后终于过了😭。不过回头写博客的时候再去看代码,也没有特别的复杂,还是得明白其中的算法逻辑是如何实现的。 Task1 - Read/Write Page Guards 简单来说,就是为Page实现一个RAII来自动管理资源。因为在BufferPoolManager::Unpin中,每次调用这个函数,都会让对应的page的pin_count_ - 1,当这个值为0时,这个page就可以被回收,或者说被替换了。但如果我们忘记去手动调用,该页面将永远不会被逐出缓冲池。由于缓冲池以更少的帧数运行,磁盘内外的页面交换将更多。不仅性能受到影响,而且很难检测到错误。 主要需要考虑如何编写移动构造、移动赋值的逻辑。移动了一个对象后,原来的对象的资源应该转移到了新对象上,那么原来的对象无法再访问资源(将原来对象的资源重置nullptr或清空)。 还有一个Drop()的接口,这是提供给使用者的释放资源的api,在实现虚构函数时可以直接调用它。Drop的实现就是调用Unpin,然后置空资源。需要注意的是,在进行移动赋值时,一开始也要Drop一下,考虑这样一种情况: auto p = std::move(basic_page_guard); p = std::move(basic_page_guard2); 这个时候同一个变量p接管了两个page,那么应该在第二个移动赋值时先drop掉第一个,因为第一个page不再使用了,自然要Unpin。 还有就是在三个page guard类重载移动赋值时,如果需要移动的对象和自身是同一个,那么直接返回自己就好: auto BasicPageGuard::operator=(BasicPageGuard &&that) noexcept -> BasicPageGuard & { if (&that == this) { return *this; } // ... 其他操作 } 在ReadPageGuard和WritePageGuard的Drop()中,还需要考虑释放管理的page的锁。对应的,锁的获取发生在FetchPageWrite()和FetchPageWrite()中。 Task2 - Extendible Hash Table Pages 为什么我们需要可扩展哈希? 下图来源:https://www.bilibili.com/video/BV1Qt421w7JT 在bustub的设计中,Header Page,Directory Page和Bucket Page都是无法直接构造出来的,即不能通过构造函数创建,只能通过各自的PageGuard中的As()或者AsMut()函数来转换。 Header header page中有一个max_depth_的成员变量,1 << max_depth_即为header page中能存放的目录的索引的数量。当我们有值需要放入哈希表时,获取hash(key)的二进制最高max_depth_位作为索引,再从header中对应位置去找到directory。对应的ExtendibleHTableHeaderPage中的功能实现并不难。 Directory directory中有两个depth: Global Depth:若global depth为n,那么这个Directory就有2^n个entry(相当于指向2^n个bucket) Local Depth:若local depth为n,则在这个对应的bucket下,每个元素的key的最后n位都相同 类似header中获取下一级页的索引,directory获取hash(key)的二进制最低global_depth_位作为索引。那local depth的作用是什么呢?...

2024-11-12 · 2 min · 282 words · Kerolt

【CMU15-445 Fall2023】Project1 Buffer Pool 小结

该系列博客只是为了记录自己在写Lab时的思路,按照课程要求不会在Github和博客中公开源代码。欢迎与我一起讨论交流! 这个Project需要我们实现一个缓存池,减少对于磁盘的频繁IO。开始慢慢上强度了,细节拉满! ...

2024-10-12 · 2 min · 295 words · Kerolt

【CMU15-445 Fall2023】Project0 C++ Primer 小结

该系列博客只是为了记录自己在写Lab时的思路,按照课程要求不会在Github和博客中公开源代码。欢迎与我一起讨论交流! project0只在task4中浅浅涉及了一点BusTub的内容,其他都是检测我们对于C++的一个掌握,主要涉及智能指针和C++的常用特性(dynamic_cast、std::move、并发与锁等)。 ...

2024-10-06 · 1 min · 65 words · Kerolt

【动手写协程库 5】常用IO函数的HOOK功能

【动手写协程库】系列笔记是学习sylar的协程库时的记录,参考了从零开始重写sylar C++高性能分布式服务器框架和代码随想录中的文档。文章并不是对所有代码的详细解释,而是为了自己理解一些片段所做的笔记。 hook函数的具体定义实现可以在这里查看:Github: src/hook.cpp 该协程库框架的目标并不是做成类似goroutine那样,而是希望能够通过协程来提高IO处理的效率。因此,对于每个文件描述符fd,我们都希望它有一个读写IO的超时时间。 hook的目的是在不重新编写代码的情况下,把老代码中的socket IO相关的API都转成异步,以提高性能。 ...

2024-09-30 · 1 min · 108 words · Kerolt

【动手写协程库 4】IO协程调度器

【动手写协程库】系列笔记是学习sylar的协程库时的记录,参考了从零开始重写sylar C++高性能分布式服务器框架和代码随想录中的文档。文章并不是对所有代码的详细解释,而是为了自己理解一些片段所做的笔记。 IOManager类中具体定义实现可以在这里查看:Github: src/iomanager.cpp 之前实现的协程调度器的功能其实非常简单,当添加任务后调度器只是单纯的从任务队列中取出任务交给协程去执行。sylar的协程库的关注对象是网络IO,如果采用这么简单的调度就根本没有用到协程的精髓。 sylar的IO协程调度解决了之前调度器在idle状态下忙等待导致CPU占用率高的问题。IO协程调度器使用一对管道fd来tickle调度协程,当调度器空闲时,idle协程通过epoll_wait阻塞在管道的读描述符上,等管道的可读事件。添加新任务时,tickle方法写管道,idle协程检测到管道可读后退出,调度器执行调度。 ...

2024-09-29 · 4 min · 782 words · Kerolt

【动手写协程库 3】定时器

【动手写协程库】系列笔记是学习sylar的协程库时的记录,参考了从零开始重写sylar C++高性能分布式服务器框架和代码随想录中的文档。文章并不是对所有代码的详细解释,而是为了自己理解一些片段所做的笔记。 TimerManager类中具体定义实现可以在这里查看:Github: src/timer.cpp 通过定时器,我们可以实现给服务器注册定时事件。sylar的定时器采用最小堆设计,所有定时器根据绝对的超时时间点(也就是超时到期的具体时间戳)进行排序,每次取出离当前时间最近的一个超时时间点,计算出超时需要等待的时间,然后等待超时。超时时间到后,获取当前的绝对时间点,然后把最小堆里超时时间点小于这个时间点的定时器都收集起来,执行它们的回调函数。 ...

2024-09-28 · 1 min · 192 words · Kerolt

enable_shared_from_this的作用

C++ 中,std::enable_shared_from_this类模板和shared_from_this成员函数主要用于在一个类的成员函数中安全地获取指向自身的std::shared_ptr。它们的作用更多是为了确保资源正确管理。 ...

2024-09-16 · 1 min · 47 words · Kerolt

【动手写协程库 2】协程调度器

【动手写协程库】系列笔记是学习sylar的协程库时的记录,参考了从零开始重写sylar C++高性能分布式服务器框架和代码随想录中的文档。文章并不是对所有代码的详细解释,而是为了自己理解一些片段所做的笔记。 Scheduler类中其他函数的定义可以在这里查看:Github: src/scheduler.cpp Sylar的协程调度器是一个N-M模型,意味着N个线程可以运行M个协程,协程能够在线程之间进行切换,也可以被绑定到特定的线程上执行。 调度器可以由应用程序中的任何线程创建,但创建它的线程(称为caller线程)可以选择是否参与协程的调度。如果caller线程参与调度,那么调度器的线程数会相应减少一个,因为caller线程本身也会作为一个调度线程。 ...

2024-09-11 · 2 min · 402 words · Kerolt

【动手写协程库 1】协程定义

【动手写协程库】系列笔记是学习sylar的协程库时的记录,参考了从零开始重写sylar C++高性能分布式服务器框架和代码随想录中的文档。文章并不是对所有代码的详细解释,而是为了自己理解一些片段所做的笔记。 Coroutine类中其他函数的定义可以在这里查看:Github: src/coroutine.cpp 对于什么是协程,为什么要使用协程,可以看看之前的笔记:【协程】C++20协程初体验。 对于我们自己来实现协程,其实在之前Xv6的Lab中就有做过:【MIT6.S081】Lab6 multithreading,当初做这个lab的时候没有意识到这就是协程。协程的切换最重要的就是要保存和恢复上下文,在这个lab中,我们通过保存每个协程在切换之前的寄存器的值,以此可用来恢复原来的执行流。 ...

2024-09-10 · 2 min · 325 words · Kerolt