PROJECT #1 - BUFFER POOL
LRUReplacer 的最大页数与缓冲池的大小相同,因为它包含 BufferPoolManager 中所有帧的占位符。 然而,在任何给定时刻,并非所有帧都被认为在 LRUReplacer 中。 LRUReplacer 被初始化为没有帧。 然后,只有 新取消固定(零引用) 的才会被认为在 LRUReplacer 中。
实施细节由您决定。 您可以使用内置的 STL 容器。 您可以假设不会耗尽内存,但必须确保操作是线程安全的。
比较简单就直接贴代码了
//===----------------------------------------------------------------------===//
//
// BusTub
//
// lru_replacer.cpp
//
// Identification: src/buffer/lru_replacer.cpp
//
// Copyright (c) 2015-2019, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//
#include "buffer/lru_replacer.h"
namespace bustub {
LRUReplacer::LRUReplacer(size_t num_pages):capacity(num_pages){}
LRUReplacer::~LRUReplacer() = default;
//删除最近最少访问的对象
bool LRUReplacer::Victim(frame_id_t *frame_id) {
latch.lock();
if (lru_map.empty()) { //如果 Replacer 为空,则返回 False
frame_id = nullptr;
latch.unlock();
return false;
}
frame_id_t lru_frame = lru_list.back(); //删掉尾部的frame_id
lru_map.erase(lru_frame);
lru_list.pop_back();
*frame_id = lru_frame; //将其内容存储在输出参数中并返回 True
latch.unlock();
return true;
}
//从LRUReplacer中删除被引用的页面。
void LRUReplacer::Pin(frame_id_t frame_id) {
latch.lock();
if (lru_map.count(frame_id) != 0) {
lru_list.erase(lru_map[frame_id]);
lru_map.erase(frame_id);
}
latch.unlock();
}
//当页面的 pin_count 变为 0 时应调用此方法。将包含页面添加到 LRUReplacer。
void LRUReplacer::Unpin(frame_id_t frame_id) {
if (lru_map.count(frame_id) != 0) return ;
latch.lock();
if (lru_map.size() == capacity) {
frame_id_t fd;
if(Victim(&fd) == false) return;
}
lru_list.push_front(frame_id); //头部插入frame_id
lru_map.insert({frame_id, lru_list.begin()}); //插入 frame_id 和 迭代器
latch.unlock();
}
size_t LRUReplacer::Size() { return lru_map.size(); }
} // namespace bustub
接下来,您需要在系统中实现缓冲池管理器(BufferPoolManagerInstance
)。BufferPoolManagerInstance
负责从DiskManager
获取数据库页面并将它们存储在内存中。BufferPoolManage
还可以在有要求它这样做时,或者当它需要驱逐一个页以便为新页腾出空间时,将脏页写入磁盘。为了确保您的实现能够正确地与系统的其余部分一起工作,我们将为您提供一些已经填写好的功能。您也不需要实现实际读写数据到磁盘的代码(在我们的实现中称为DiskManager
)。我们将为您提供这一功能。
系统中的所有内存页面均由Page
对象表示。BufferPoolManagerInstance
不需要了解这些页面的内容。 但是,作为系统开发人员,重要的是要了解Page
对象只是缓冲池中用于存储内存的容器,因此并不特定于唯一页面。 也就是说,每个Page
对象都包含一块内存,DiskManager
会将其用作复制从磁盘读取的物理页面内容的位置。 BufferPoolManagerInstance
将在将其来回移动到磁盘时重用相同的Page对象来存储数据。 这意味着在系统的整个生命周期中,相同的Page
对象可能包含不同的物理页面。Page
对象的标识符(page_id
)跟踪其包含的物理页面。 如果Page
对象不包含物理页面,则必须将其page_id
设置为INVALID_PAGE_ID
。
每个Page对象还维护一个计数器,以显示“固定”该页面的线程数。BufferPoolManagerInstance
不允许释放固定的页面。每个Page
对象还跟踪它的脏标记。您的工作是判断页面在解绑定之前是否已经被修改(修改则把脏标记置为1)。BufferPoolManagerInstance
必须将脏页的内容写回磁盘,然后才能重用该对象。
BufferPoolManagerInstance
实现将使用在此分配的前面步骤中创建的LRUReplacer
类。它将使用LRUReplacer
来跟踪何时访问页对象,以便在必须释放一个帧来为从磁盘复制新的物理页腾出空间时,它可以决定取消哪个页对象
You will need to implement the following functions defined in the header file (src/include/buffer/buffer_pool_manager_instance.h
) in the source file (src/buffer/buffer_pool_manager_instance.cpp
):
FetchPgImp(page_id)
Fetch the requested page from the buffer pool.UnpinPgImp(page_id, is_dirty)
Unpin the target page from the buffer pool.FlushPgImp(page_id)
Flushes the target page to disk.NewPgImp(page_id)
Creates a new page in the buffer pool.DeletePgImp(page_id)
Deletes a page from the buffer pool.FlushAllPagesImpl()
Flushes all the pages in the buffer pool to disk.先看看构造函数和数据
BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, uint32_t num_instances, uint32_t instance_index, DiskManager *disk_manager, LogManager *log_manager)
: pool_size_(pool_size),
num_instances_(num_instances),
next_page_id_(instance_index),
disk_manager_(disk_manager),
log_manager_(log_manager) {
BUSTUB_ASSERT(num_instances > 0, "If BPI is not part of a pool, then the pool size should just be 1");
BUSTUB_ASSERT(instance_index < num_instances,"BPI index cannot be greater than the number of BPIs in the pool. In non-parallel case, index should just be 1.");
// We allocate a consecutive memory space for the buffer pool.
pages_ = new Page[pool_size_];
replacer_ = new LRUReplacer(pool_size);
// Initially, every page is in the free list.
for (size_t i = 0; i < pool_size_; ++i) {
free_list_.emplace_back(static_cast(i));
}
}
//BufferPoolManager的数据
/** Array of buffer pool pages. */
Page *pages_;
/** Pointer to the disk manager. */
DiskManager *disk_manager_ __attribute__((__unused__));
/** Pointer to the log manager. */
LogManager *log_manager_ __attribute__((__unused__));
/** Page table for keeping track of buffer pool pages. */
std::unordered_map page_table_;
/** Replacer to find unpinned pages for replacement. */
Replacer *replacer_;
/** List of free pages. */
std::list free_list_;
/** This latch protects shared data structures. We recommend updating this comment to describe what it protects. */
std::mutex latch_;
//page的数据
/** The actual data that is stored within a page. */
char data_[PAGE_SIZE]{};
/** The ID of this page. */
page_id_t page_id_ = INVALID_PAGE_ID;
/** The pin count of this page. */
int pin_count_ = 0;
/** True if the page is dirty, i.e. it is different from its corresponding page on disk. */
bool is_dirty_ = false;
/** Page latch. */
ReaderWriterLatch rwlatch_;
Memory(Buffer Pool Management)
面向磁盘的数据库有一个重要的特性就是其数据不能全部装入内存。
所以只能将将数据部分装入内存,并向上提供全部装入内存的幻象(有点虚拟内存的感觉)。而这就是通过buffer pool management来提供的。
与虚拟内存的设计思想一致,BPM核心的便是将内存中的地址映射到磁盘中的地址(再次通过增加一个中间层解决了问题),向上提供所有数据都装入内存的幻象,数据单位为页。为了区分内存中的页和磁盘中的页,我们做一个定义:
BPM结构如下图所示:
其中包含两个数据结构:
实际运行的流程如下 (仅为请求) :
上层任务向BPM请求一个page
BPM查询对应的page是否在buffer pool中,即Page Table中包不包含page id
- 如果包含,那么返回对应的frame地址,并将frame 的pin count加1(代表这个frame的引用多了一次)
- 如果不包含,BPM需要将Page读如Buffer Pool中的frame中,再返回对应的frame地址(这其中可能包含置换)
结束
先写有注释的函数
BufferPoolManagerInstance
不允许释放固定的页面。每个Page
对象还跟踪它的脏标记。您的工作是判断页面在解绑定之前是否已经被修改(修改则把脏标记置为1)。BufferPoolManagerInstance
必须将脏页的内容写回磁盘,然后才能重用该对象。BufferPoolManagerInstance
实现将使用在此分配的前面步骤中创建的LRUReplacer
类。它将使用LRUReplacer
来跟踪何时访问页对象,以便在必须释放一个帧来为从磁盘复制新的物理页腾出空间时,它可以决定取消哪个页对象这个计数器不为0意味着一直有线程在使用这块内存,所以是不被允许替换的,其次在替换的时候
在必要时(BufferPool满了的时候),根据LRU算法选择替换一个页面
FetchPage(获取page),若在Buffer Pool中,直接返回对象。若不在,则从磁盘中加载后返回对象。
bool BufferPoolManagerInstance::lru_replace(frame_id_t *frame_id) {
if (!free_list_.empty()) { //直接取出frame_id
*frame_id = free_list_.front();
free_list_.pop_front();
return true;
}
if (replacer_->Victim(frame_id) == false) { return false; } //buffer_pool的所有页都被pin了
int replace_frame_id = -1;
for (auto &p : page_table_) { //遍历所有的page_table,查找id为frame_id的frame
page_id_t pid = p.first;
frame_id_t fid = p.second;
if (fid == *frame_id) {
replace_frame_id = pid;
break;
}
}
if (replace_frame_id == -1) { return false;} //buffer_pool里没有这个frame
Page *replace_page = &pages_[*frame_id];
if (replace_page->is_dirty_) { //脏位
char *data = pages_[page_table_[replace_page->page_id_]].data_;
disk_manager_->WritePage(replace_page->page_id_, data);
replace_page->pin_count_ = 0; //重置pin_count
}
page_table_.erase(replace_page->page_id_); //从映射中删除
return true;
}
FetchPgImp(page_id)
Fetch the requested page from the buffer pool.
从缓冲池中请求一个页面,则该页面
Page *BufferPoolManagerInstance::FetchPgImp(page_id_t page_id) {
// 1. Search the page table for the requested page (P).
// 1.1 If P exists, pin it and return it immediately.
// 1.2 If P does not exist, find a replacement page (R) from either the free list or the replacer.
// Note that pages are always found from the free list first.
// 2. If R is dirty, write it back to the disk.
// 3. Delete R from the page table and insert P.
// 4. Update P's metadata, read in the page content from disk, and then return a pointer to P.
latch_.lock();
// 1. Search the page table for the requested page (P).
auto it = page_table_.find(page_id);
if (it != page_table_.end()) {
// 1.1
frame_id_t frame_id = it->second;
Page *page = &pages_[frame_id];
page->pin_count_++; // pin the page
replacer_->Pin(frame_id); // notify replacer
latch_.unlock();
return page;
}
// 1.2
frame_id_t replace_fid;
if (!lru_replace(&replace_fid)) { //替换失败
latch_.unlock();
return nullptr;
}
Page *replacePage = &pages_[replace_fid];
// 2.
if (replacePage->IsDirty()) {
disk_manager_->WritePage(replacePage->page_id_, replacePage->data_);
}
// 3. Delete R from the page table and insert P.
page_table_.erase(replacePage->page_id_);
page_table_[page_id] = replace_fid;
// 4. Update P's metadata, read in the page content from disk, and then return a pointer to P.
Page *newPage = replacePage; //创建一个新的帧
disk_manager_->ReadPage(page_id, newPage->data_); //读磁盘到内存中
newPage->page_id_ = page_id;
newPage->pin_count_++;
newPage->is_dirty_ = false;
replacer_->Pin(replace_fid);
latch_.unlock();
return newPage;
}
NewPageImpl 实现
分配一个新的page。
find_replace
函数在我们的缓冲池找到合适的地方建立page_id --> frame_id的映射Page *BufferPoolManager::NewPageImpl(page_id_t *page_id) {
// 0. Make sure you call AllocatePage!
// 1. If all the pages in the buffer pool are pinned, return nullptr.
// 2. Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
// 3. Update P's metadata, zero out memory and add P to the page table.
// 4. Set the page ID output parameter. Return a pointer to P.
latch_.lock();
// 0.
page_id_t new_page_id = disk_manager_->AllocatePage();
// 1.
bool is_all = true;
for (int i = 0; i < static_cast(pool_size_); i++) {
if (pages_[i].pin_count_ == 0) {
is_all = false;
break;
}
}
if (is_all) {
latch_.unlock();
return nullptr;
}
// 2.
frame_id_t victim_fid;
if (!find_replace(&victim_fid)) {
latch_.unlock();
return nullptr;
}
// 3.
Page *victim_page = &pages_[victim_fid];
victim_page->page_id_ = new_page_id;
victim_page->pin_count_++;
replacer_->Pin(victim_fid);
page_table_[new_page_id] = victim_fid;
victim_page->is_dirty_ = false;
*page_id = new_page_id;
disk_manager_->WritePage(victim_page->GetPageId(), victim_page->GetData());
latch_.unlock();
return victim_page;
}
DeletePageImpl 实现
这里是要我们把缓冲池中的page移出
bool BufferPoolManager::DeletePageImpl(page_id_t page_id) {
// 0. Make sure you call DeallocatePage!
// 1. Search the page table for the requested page (P).
// 1. If P does not exist, return true.
// 2. If P exists, but has a non-zero pin-count, return false. Someone is using the page.
// 3. Otherwise, P can be deleted. Remove P from the page table, reset its metadata and return it to the free list.
latch_.lock();
// 1.
if (page_table_.find(page_id) == page_table_.end()) {
latch_.unlock();
return true;
}
//2.
frame_id_t frame_id = page_table_[page_id];
Page *page = &pages_[frame_id];
if (page->pin_count_ > 0) {
latch_.unlock();
return false;
}
// if dirty write to disk
if (page->is_dirty_) {
FlushPageImpl(page_id);
}
disk_manager_->DeallocatePage(page_id);
page_table_.erase(page_id);
// reset metadata
page->is_dirty_ = false;
page->pin_count_ = 0;
page->page_id_ = INVALID_PAGE_ID;
// return it to the free list
free_list_.push_back(frame_id);
latch_.unlock();
return true;
}
UnpinPgImp(page_id, is_dirty)
Unpin the target page from the buffer pool.
解引用操作
pin_couter>0
我们直接–pin _couter==0
我们需要给它加到Lru_replacer
中。因为没有人引用它。所以它可以成为被替换的候选人bool BufferPoolManager::UnpinPageImpl(page_id_t page_id, bool is_dirty) {
latch_.lock();
// 1. 如果page_table中就没有
auto iter = page_table_.find(page_id);
if (iter == page_table_.end()) {
latch_.unlock();
return false;
}
// 2. 找到要被unpin的page
frame_id_t unpinned_Fid = iter->second;
Page *unpinned_page = &pages_[unpinned_Fid];
if (is_dirty) {
unpinned_page->is_dirty_ = true;
}
// if page的pin_count == 0 则直接return
if (unpinned_page->pin_count_ == 0) {
latch_.unlock();
return false;
}
unpinned_page->pin_count_--;
if (unpinned_page->GetPinCount() == 0) {
replacer_->Unpin(unpinned_Fid);
}
latch_.unlock();
return true;
}
FlushPageImpl 实现
bool BufferPoolManagerInstance::FlushPgImp(page_id_t page_id) {
// Make sure you call DiskManager::WritePage!
auto iter = page_table_.find(page_id);
if (iter == page_table_.end() || page_id == INVALID_PAGE_ID) {
latch_.unlock();
return false;
}
frame_id_t flush_fid = iter->second;
disk_manager_->WritePage(page_id, pages_[flush_fid].data_);
pages_[flush_fid].is_dirty_ = false;
return false;
return false;
}
FlushAllPgsImp实现
void BufferPoolManagerInstance::FlushAllPgsImp() {
// You can do it!
for (auto it : page_table_) {
page_id_t page_id = it.first;
frame_id_t flush_fid = it.second;
disk_manager_->WritePage(page_id, pages_[flush_fid].data_);
pages_[flush_fid].is_dirty_ = false;
}
}
测试
make buffer_pool_manager_instance_test
./test/buffer_pool_manager_instance_test
正如您在前面的任务中可能注意到的那样,单个缓冲区池管理器实例需要使用闩锁才能确保线程安全。当每个线程在与缓冲池交互时争夺一个闩锁时,这可能会引起很多争用。一个潜在的解决方案是在您的系统中有多个缓冲池,每个缓冲池都有自己的锁存器。
ParallelBufferPoolManager
是一个包含多个BufferPoolManagerInstance
对于每一次行动,ParallelBufferPoolManager
挑一个BufferPoolManagerInstance
代表那个例子。
我们使用给定的页面id来确定特定的BufferPoolManagerInstance
利用。如果我们有num_instances
许多BufferPoolManagerInstance
然后,我们需要一些方法将给定的页面id映射到范围[0,num_instents)中的一个数字。对于这个项目,我们将使用模块化操作符,page_id mod num_instances
将给定的page_id映射到正确的范围。
当ParallelBufferPoolManager
首先实例化它的起始索引为0。每次创建新页面时,您都会尝试BufferPoolManagerInstance
,从起始指数开始,直到成功为止。然后将起始索引增加一个。
确保在创建个人时BufferPoolManagerInstance
S您使用的构造函数uint32_t num_instances
和uint32_t instance_index
这样就可以正确地创建页面ID。
您需要实现在头文件中定义的下列函数(Src/include/buffer/parallel_buffer_pool_manager.h
)在源文件中(SRC/缓冲器/并行缓冲池_Manager.cpp
):
ParallelBufferPoolManager(num_instances, pool_size, disk_manager, log_manager)
~ParallelBufferPoolManager()
GetPoolSize()
GetBufferPoolManager(page_id)
FetchPgImp(page_id)
UnpinPgImp(page_id, is_dirty)
FlushPgImp(page_id)
NewPgImp(page_id)
DeletePgImp(page_id)
FlushAllPagesImpl()
//BufferPoolManagerInstance的数据
/** Number of pages in the buffer pool. */
const size_t pool_size_;
/** How many instances are in the parallel BPM (if present, otherwise just 1 BPI) */
const uint32_t num_instances_ = 1;
/** Index of this BPI in the parallel BPM (if present, otherwise just 0) */
const uint32_t instance_index_ = 0;
/** Each BPI maintains its own counter for page_ids to hand out, must ensure they mod back to its instance_index_ */
std::atomic next_page_id_ = instance_index_;
修改 构造函数
BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, uint32_t num_instances, uint32_t instance_index,
DiskManager *disk_manager, LogManager *log_manager)
: pool_size_(pool_size),
num_instances_(num_instances),
instance_index_(instance_index), //原本为next_page_id_
disk_manager_(disk_manager),
log_manager_(log_manager)
private:
std::vector bufferPool;
ParallelBufferPoolManager::ParallelBufferPoolManager(size_t num_instances, size_t pool_size, DiskManager *disk_manager,
LogManager *log_manager) {
// Allocate and create individual BufferPoolManagerInstances
for (long unsigned int i = 0; i < num_instances; i++) {
bufferPool.push_back(new BufferPoolManagerInstance(pool_size, num_instances, i, disk_manager, log_manager));
}
}
// Update constructor to destruct all BufferPoolManagerInstances and deallocate any associated memory
ParallelBufferPoolManager::~ParallelBufferPoolManager() {
for (int i = 0; i < bufferPool.size(); i++) {
for (auto it : page_table_) {
page_id_t page_id = it.first;
frame_id_t flush_fid = it.second;
disk_manager_->WritePage(page_id, pages_[flush_fid].data_);
pages_[flush_fid].is_dirty_ = false;
}
DeallocatePage(page_id);
delete bufferPool[i];
}
}
其他按照指引填写即可
测试
make parallel_buffer_pool_manager_test
./test/parallel_buffer_pool_manager_test
成功完成
21: Test command: /home/knight/Desktop/cmu15-445/build/test/type_test "--gtest_color=yes" "--gtest_output=xml:/home/knight/Desktop/cmu15-445/build/test/type_test.xml"
21: Test timeout computed to be: 10000000
21: Running main() from gmock_main.cc
21: [==========] Running 5 tests from 1 test suite.
21: [----------] Global test environment set-up.
21: [----------] 5 tests from TypeTests
21: [ RUN ] TypeTests.InvalidTypeTest
21:
21: Exception Type :: Unknown Type
21: Message :: Unknown type.
21:
21: Exception Type :: Mismatch Type
21: Message :: Cannot get minimal value.
21:
21: Exception Type :: Mismatch Type
21: Message :: Cannot get max value.
21: [ OK ] TypeTests.InvalidTypeTest (0 ms)
21: [ RUN ] TypeTests.GetInstanceTest
21: [ OK ] TypeTests.GetInstanceTest (0 ms)
21: [ RUN ] TypeTests.MaxValueTest
21: [ OK ] TypeTests.MaxValueTest (0 ms)
21: [ RUN ] TypeTests.MinValueTest
21: [ OK ] TypeTests.MinValueTest (0 ms)
21: [ RUN ] TypeTests.TemplateTest
21: size is 48
21: key info32
21: [ OK ] TypeTests.TemplateTest (0 ms)
21: [----------] 5 tests from TypeTests (0 ms total)
21:
21: [----------] Global test environment tear-down
21: [==========] 5 tests from 1 test suite ran. (0 ms total)
21: [ PASSED ] 5 tests.
21/21 Test #21: type_test ........................... Passed 0.02 sec
100% tests passed, 0 tests failed out of 21
Total Test time (real) = 0.53 sec
[100%] Built target check-tests