CMU_15445__2023Fall_Project0

这一个Project是让我们了解C++的语法以及改数据库项目的整体框架, 基本的锁的使用, 怎么Debug. 一些零碎的知识碎片我放到最后了, 以前是写C的, C++的很多语法还不是很熟悉, 很多新的语法更不知道该怎么用. 这次作业完成也是受益良多.

Copy_on_Write 字典树

Copy_on_Write又称为写时拷贝, 在Linux中新创建一个进程的fork()函数, 对父进程的页表拷贝的时候, 使用的就是该方法, 父进程与子进程开始公用一个页表, 当子进程的页表需要写入的时候才会拷贝一份新的页表副本. 所以在实现字典树修改的Copy_on_Write的时候.

读这颗树是不需要进程拷贝的, 而所谓的写时复制, 简单地说就是对需要修改的树进行copy, 例如, 插入一个 <key, vaule> 的时候, 在搜索过程中, 从根节点到叶子节点的途中, 叶子节点改变, 意味着以中途节点作为根节点的子树都改变了, 所以每一个中途节点都需要拷贝.

Get(key) 的实现

Get()函数的算法原理很简单, 之前有一点Java的面向对象编程的基础就可以了, 这里比较难的是我还没有使用过在C++中如何使用智能指针的转换, 将指向父类的指针指向派生类, 然后访问派生类的元素. 使用智能指针的std::dynamic_pointer_cast方法.
dynamic_pointer_cast()返回一个指向派生类的指针, 有一点需要注意的是, 在TrieNodeWithValue类中, value 也是智能指针, 需要使用get() 得到原始指针作为函数的返回值.

Put(key, value) 的实现

Put() 函数的流程就是Copy_on_Write的实现的典型实例. 基本思想就是按照key的路径向下访问字典树, 然后途中遇到的节点都是会修改的子树的根节点, 所以需要Copy操作, Copy操作的实现, 在TrieNode中已经给出了, 实际上就是Clone() 函数.

可以用下图的实例看出, Put("time", 6) 的时候, 实际上插入节点后, 以中途节点作为根节点的子树都是改变了的, 所以这些节点需要Copy. 实现方式也不难.
Put

Remove(key) 的实现

我的Remove()方法实际上是参考了别人的方法, 基本思想分成两步:

  1. 找到需要remove的节点, 通过遍历原始字典是实现
  2. 拷贝原来的树, 直到遇到需要删除的节点为止, 到达该节点后不再复制节点, 并删除多余的节点.
    这里我将它分成三种情况:
  3. 需要删除的节点为根节点, 直接返回一颗空树
  4. 删除节点是key指向的节点
  5. 删除的节点是key节点的父节点, 假设key节点的value删除之后, 从该节点到key节点的所有中途节点均只有一个孩子节点, 且不是值节点.
    我们可以用下图展示第二种情况与第三种情况:
    Remove
    需要注意的是, Remove(key) 函数, 如果该节点被删除过, 那么该方法可能不做任何操作.

Concurrent Key-Value Store

这个比较简单, 这里最复杂的反而是GET(), 因为需要使用了ValueGuard(), 它的作用是TrieStore::Get 方法在获取 Trie 的根节点并进行查找后, 返回一个 ValueGuard 对象, 这个对象持有对查找到的值和 Trie 根节点的引用. 这样做可以确保:

  1. 值的有效性: ValueGuard 会在其生命周期内持有 Trie 的引用, 防止 Trie 在此期间被修改或释放. 这就意味着只要 ValueGuard 还存在, 查找到的值就会保持有效, 防止多线程其他线程的插入或者删除的修改.
  2. 资源管理: 当 ValueGuard 的生命周期结束时, 它会自动处理引用的释放或恢复操作, 从而避免手动管理生命周期带来的复杂性和潜在的错误.

vscode Debug 的方式

这一步骤我使用的是vscode debug, 配合使用cout输出, 很方便.
这个问题我最让我花时间的地方是, 之前对cmake以及make的测试不够熟悉. 这里我测试的方式是进入CMU_15-445/build/test目录下, 然后我们可以找到每一个需要测试的测试文件, 实际上Project下面的Testing里面也有写, 我当时没注意看, 后来全靠自己猜的. 实际只需要进入CMU_15-445/build/目录下面就可以测试了.

这里make之后, 生成可执行文件, 就可以使用vscode配置调试了, vscode配置编译与调试分别是task.jsonlaunch.json 两个文件.
这个可以根据官网来配置, 配置好了之后可以直接在vscode中调试, launch.json中有一个衔接task的配置, 方便每次调试之前都编译一次, 获取最新的编译后的程序调试, 就是preLaunchTask字段, 官网的说明是:

preLaunchTask - to launch a task before the start of a debug session, set this attribute to the label of a task specified in tasks.json (in the workspace's .vscode folder). Or, this can be set to ${defaultBuildTask} to use your default build task.

我还发现, vscode调试的时候, 看不到 value, 这里应该是vscode默认使用父类TrieNode的指针, 父类的指针看不到派生类的元素, 所以我还是用cout看的输出.

SQL String Functions

这一步开始涉及到数据库的实际操作以及整体框架了. 是完善字符串表达式, 这部分还有很多没看懂, 例如框架bustub是怎么获取输入表达式的, 然后还有数据库的Evaluate这些函数是干什么的, 但是这些不知道不影响完成这部分题目, 直接看注释就可以了.

看着注释就可以很简单的实现注释中的部分了, 我的实现如下:

// NOLINTNEXTLINE
auto Planner::GetFuncCallFromFactory(const std::string &func_name, std::vector<AbstractExpressionRef> args)
    -> AbstractExpressionRef {
  if (func_name != "lower" && func_name != "upper") {
    throw Exception(fmt::format("func call {} not supported in planner yet", func_name));
  }
  if (args.size() != 1) {
    throw Exception(fmt::format("func call {} not supported in planner yet", func_name));
  }

  // 1. check if the parsed function name is "lower" or "upper".
  // 2. verify the number of args (should be 1), refer to the test cases for when you should throw an `Exception`.
  // 3. return a `StringExpression` std::shared_ptr.
  StringExpressionType type =
      func_name == "lower" ? bustub::StringExpressionType::Lower : bustub::StringExpressionType::Upper;
  return std::make_shared<StringExpression>(args[0], type);
}
posted @ 2024-05-21 08:42  虾野百鹤  阅读(14)  评论(0编辑  收藏  举报