[满分]CMU-15/445[2021fall]PROJECT #1
作者:互联网
PROJECT #1 - BUFFER POOL
TASK #1 - LRU REPLACEMENT POLICY
需要实现LRU策略。实现如下函数:
Victim(frame_id_t *)
:通过最近最少使用方法挑选一个牺牲的帧,牺牲帧的id放在传出参数中,如果挑选成功,就返回true,否则返回false。Pin(frame_id_t)
:将一个帧从牺牲候选队列中移除。Unpin(frame_id_t)
:将一个帧加入牺牲候选队列。Size()
:返回当前牺牲候选队列的大小。
Pin
和Unpin
的含义比较令人疑惑,函数的名字是根据Task2中的功能来取的。当从缓冲池中读取一个页时,为了不让其他操作把这个页替换掉,我们需要"Pin"
住这个页,不让它被替换(牺牲)。Unpin
与之相反,表明我们不再需要这个页, 此页可以被牺牲。
需要注意的是,Pin
一个页或Unpin
一个页可能重复操作,需要进行处理。同时,Unpin
一个操作发生前,此帧已经在牺牲候选队列中,不会对其进行移动!。
错误想法可能是
Unpin
一个帧,无论是否在队列中,都会将其移动到队列头。通过观察测试代码可以确认函数的功能。
为了O(1)时间进行查找,可以用Hash + LinkList的实现方式。
1.1 数据结构
// lru牺牲队列的最大容量
const size_t capacity_;
std::mutex latch_;
// 牺牲队列
std::list<frame_id_t> lru_list_;
// 将frame_id和其对应的list的迭代器对应起来
std::unordered_map<frame_id_t, std::list<frame_id_t>::iterator> lru_map_;
1.2 VictimHelper函数
编写无锁的,挑选牺牲帧的帮助函数。其合法性由调用者控制。
frame_id_t LRUReplacer::VictimeHelper() {
// 从list尾拿取被牺牲帧号。
// 将此帧从list和map中移除
frame_id_t victime_frame = lru_list_.back();
lru_list_.pop_back();
lru_map_.erase(victime_frame);
return victime_frame;
}
1.3 Victim函数
如果没有可以牺牲的帧直接返回false,否则调用帮助函数拿取牺牲帧。
bool LRUReplacer::Victim(frame_id_t *frame_id) {
std::lock_guard<std::mutex> guard(latch_);
if (lru_map_.empty()) {
*frame_id = -1;
return false;
}
*frame_id = VictimeHelper();
return true;
}
1.4 Pin函数
Pin
表示这个frame被引用了,被引用的frame不能成为LRU的牺牲目标。
void LRUReplacer::Pin(frame_id_t frame_id) {
std::lock_guard<std::mutex> guard(latch_);
if (lru_map_.find(frame_id) == lru_map_.end()) {
return;
}
lru_list_.erase(lru_map_.find(frame_id)->second);
lru_map_.erase(frame_id);
}
1.5 Unpin函数
Unpin
表示该页不再使用了,可以被替换出去,加到牺牲队列中。如果此前已在队列中,就不操作。
void LRUReplacer::Unpin(frame_id_t frame_id) {
std::lock_guard<std::mutex> guard(latch_);
if (lru_map_.find(frame_id) != lru_map_.end()) {
return;
}
while (lru_map_.size() >= capacity_) {
VictimeHelper();
}
lru_list_.push_front(frame_id);
lru_map_[frame_id] = lru_list_.begin();
}
1.6 Size函数
size_t LRUReplacer::Size() {
std::lock_guard<std::mutex> guard(latch_);
return lru_map_.size();
}
TASK #2 - BUFFER POOL MANAGER INSTANCE
实现一个缓冲池管理器,负责从磁盘中读取数据库页并存储在内存中。系统中所有的内存页都由页对象表示。系统整个生命周期中,重用内存位置,相同的frame位置(包含一个Page对象)可能对应不同的页面。Page对象的标识符(page_id)表示其指向的页面,如果不包含,则设置为INVALID_PAGE_ID
。每个Page对象维护一个计数器,以显式Pin
该页面的线程数,不允许释放被Pin
的页面。每个Page对象还有脏页标记,只有将脏页写回磁盘,才能重用。
需要实现如下函数:
FetchPgImp(page_id)
:向缓冲池请求对应页。UnpinPgImp(page_id, is_dirty)
:Unpin对应页。FlushPgImp(page_id)
:将目标页刷回磁盘。NewPgImp(page_id)
:在缓冲池中创建新的页。DeletePgImp(page_id)
:删除页。FlushAllPagesImp()
:所有页刷新回磁盘。
2.1 FindReplaceFrame函数
无锁的帮助函数,选取一个帧用于存储新的页。
- 如果空闲页列表和LRU列表都为空,则无法找到位置存储新的页,返回false。
- 否则优先在空闲页列表中查找,然后在LRU中查找。
- 如果LRU中选择牺牲页,则将脏页刷新回磁盘,重置Page中的数据。
bool BufferPoolManagerInstance::FindReplaceFrame(frame_id_t *frame_id) {
if (free_list_.empty() && replacer_->Size() == 0) {
return false;
}
if (!free_list_.empty()) {
*frame_id = free_list_.back();
free_list_.pop_back();
} else {
bool is_victim = replacer_->Victim(frame_id);
if (!is_victim) {
return false;
}
page_id_t page_id = pages_[*frame_id].page_id_;
page_table_.erase(page_id);
pages_[*frame_id].WLatch();
if (pages_[*frame_id].IsDirty()) {
disk_manager_->WritePage(page_id, pages_[*frame_id].data_);
}
pages_[*frame_id].ResetMemory();
pages_[*frame_id].page_id_ = INVALID_PAGE_ID;
pages_[*frame_id].pin_count_ = 0;
pages_[*frame_id].is_dirty_ = false;
pages_[*frame_id].WUnlatch();
}
return true;
}
2.2 FlushPgImp函数
如果page_id是无效值或者page_table_中找不到page_id,返回false。
bool BufferPoolManagerInstance::FlushPgImp(page_id_t page_id) {
// Make sure you call DiskManager::WritePage!
std::lock_guard<std::mutex> guard(latch_);
if (page_id == INVALID_PAGE_ID || page_table_.find(page_id) == page_table_.end()) {
return false;
}
Page &rp = pages_[page_table_.find(page_id)->second];
rp.RLatch();
disk_manager_->WritePage(rp.page_id_, rp.data_);
rp.RUnlatch();
return true;
}
2.3 FlushAllPgsImp函数
void BufferPoolManagerInstance::FlushAllPgsImp() {
// You can do it!
std::lock_guard<std::mutex> guard(latch_);
for (auto &iterat : page_table_) {
Page &rp = pages_[iterat.second];
rp.RLatch();
disk_manager_->WritePage(rp.page_id_, rp.data_);
rp.RUnlatch();
}
}
2.4 NewPgImp函数
- 需要注意的是要首先找到空闲页或牺牲页再分配页号。
- 确认要占用的帧号后写入信息,写入磁盘。
Page *BufferPoolManagerInstance::NewPgImp(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.
std::lock_guard<std::mutex> guard(latch_);
frame_id_t frame_id = -1;
if (!FindReplaceFrame(&frame_id)) {
return nullptr;
}
*page_id = AllocatePage();
page_table_[*page_id] = frame_id;
pages_[frame_id].WLatch();
pages_[frame_id].page_id_ = *page_id;
++pages_[frame_id].pin_count_;
disk_manager_->WritePage(pages_[frame_id].page_id_, pages_[frame_id].data_);
pages_[frame_id].WUnlatch();
replacer_->Pin(frame_id); // 冗余、逻辑清晰
return &(pages_[frame_id]);
}
2.5 FetchPgImp函数
- 首先确认是否已经在缓冲池中,如果有直接返回内存地址。
- 否则寻找放置页的位置。如果不在缓冲池中,要从磁盘中读取数据。
- 增加pin计数。
- 将此页从LRU列表中移除(冗余、逻辑清晰)。
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.
std::lock_guard<std::mutex> guard(latch_);
frame_id_t frame_id = -1;
if (page_table_.find(page_id) != page_table_.end()) {
frame_id = page_table_.find(page_id)->second;
} else {
if (!FindReplaceFrame(&frame_id)) {
return nullptr;
}
pages_[frame_id].WLatch();
disk_manager_->ReadPage(page_id, pages_[frame_id].data_);
pages_[frame_id].page_id_ = page_id;
pages_[frame_id].WUnlatch();
page_table_[page_id] = frame_id;
}
pages_[frame_id].WLatch();
++pages_[frame_id].pin_count_;
pages_[frame_id].WUnlatch();
replacer_->Pin(frame_id);
return &(pages_[frame_id]);
}
2.6 DeletePgImp函数
- 要Pin被移除的帧号,避免空闲列表和LRU列表中出现相同的帧号。
- 如果是脏页,刷新回磁盘。
- 换源此区域数据。
bool BufferPoolManagerInstance::DeletePgImp(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.
std::lock_guard<std::mutex> guard(latch_);
if (page_table_.find(page_id) == page_table_.end()) {
return true;
}
frame_id_t frame_id = page_table_.find(page_id)->second;
pages_[frame_id].WLatch();
if (pages_[frame_id].pin_count_ > 0) {
pages_[frame_id].WUnlatch();
return false;
}
if (pages_[frame_id].IsDirty()) {
disk_manager_->WritePage(pages_[frame_id].page_id_, pages_[frame_id].data_);
}
pages_[frame_id].ResetMemory();
pages_[frame_id].page_id_ = INVALID_PAGE_ID;
pages_[frame_id].pin_count_ = 0;
pages_[frame_id].is_dirty_ = false;
pages_[frame_id].WUnlatch();
page_table_.erase(page_id);
DeallocatePage(page_id);
free_list_.push_front(frame_id);
replacer_->Pin(frame_id);
return true;
}
2.7 UnpinPgImp函数
- 注意不能直接将
is_dirty_
变量赋值为is_dirty
,否则先true后false会被覆盖。
bool BufferPoolManagerInstance::UnpinPgImp(page_id_t page_id, bool is_dirty) {
std::lock_guard<std::mutex> guard(latch_);
if (page_table_.find(page_id) == page_table_.end()) {
return false;
}
frame_id_t frame_id = page_table_.find(page_id)->second;
pages_[frame_id].WLatch();
if (pages_[frame_id].pin_count_ <= 0) {
pages_[frame_id].WUnlatch();
return false;
}
--pages_[frame_id].pin_count_;
pages_[frame_id].is_dirty_ = is_dirty || pages_[frame_id].is_dirty_;
if (pages_[frame_id].pin_count_ == 0) {
replacer_->Unpin(frame_id);
}
pages_[frame_id].WUnlatch();
return true;
}
TASK #3 - PARALLEL BUFFER POOL MANAGER
需要实现并行缓冲池,较简单。除NewPgImp
外,只需取模计算应当分配给哪个缓冲池实例然后调用即可。不需要加锁,只需将保存start_index_变量设为原子变量即可。
const size_t num_instances_;
const size_t instance_pool_size_;
std::vector<BufferPoolManager *> pool_instances_;
std::atomic<size_t> start_index_ = 0;
ParallelBufferPoolManager::ParallelBufferPoolManager(size_t num_instances, size_t pool_size, DiskManager *disk_manager,
LogManager *log_manager)
: num_instances_(num_instances), instance_pool_size_(pool_size) {
// Allocate and create individual BufferPoolManagerInstances
for (size_t i = 0; i < num_instances; ++i) {
pool_instances_.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 (auto &pool_instance : pool_instances_) {
delete pool_instance;
}
}
size_t ParallelBufferPoolManager::GetPoolSize() {
// Get size of all BufferPoolManagerInstances
return num_instances_ * instance_pool_size_;
}
BufferPoolManager *ParallelBufferPoolManager::GetBufferPoolManager(page_id_t page_id) {
// Get BufferPoolManager responsible for handling given page id. You can use this method in your other methods.
return pool_instances_[page_id % num_instances_];
}
Page *ParallelBufferPoolManager::FetchPgImp(page_id_t page_id) {
// Fetch page for page_id from responsible BufferPoolManagerInstance
return GetBufferPoolManager(page_id)->FetchPage(page_id);
}
bool ParallelBufferPoolManager::UnpinPgImp(page_id_t page_id, bool is_dirty) {
// Unpin page_id from responsible BufferPoolManagerInstance
return GetBufferPoolManager(page_id)->UnpinPage(page_id, is_dirty);
}
bool ParallelBufferPoolManager::FlushPgImp(page_id_t page_id) {
// Flush page_id from responsible BufferPoolManagerInstance
return GetBufferPoolManager(page_id)->FlushPage(page_id);
}
Page *ParallelBufferPoolManager::NewPgImp(page_id_t *page_id) {
// create new page. We will request page allocation in a round robin manner from the underlying
// BufferPoolManagerInstances
// 1. From a starting index of the BPMIs, call NewPageImpl until either 1) success and return 2) looped around to
// starting index and return nullptr
// 2. Bump the starting index (mod number of instances) to start search at a different BPMI each time this function
// is called
Page *res = nullptr;
for (size_t i = 0; i < num_instances_; ++i) {
if ((res = pool_instances_[(i + start_index_) % num_instances_]->NewPage(page_id)) != nullptr) {
break;
}
}
start_index_ += 1;
return res;
}
bool ParallelBufferPoolManager::DeletePgImp(page_id_t page_id) {
// Delete page_id from responsible BufferPoolManagerInstance
return GetBufferPoolManager(page_id)->DeletePage(page_id);
}
void ParallelBufferPoolManager::FlushAllPgsImp() {
// flush all pages from all BufferPoolManagerInstances
for (size_t i = 0; i < num_instances_; ++i) {
pool_instances_[i]->FlushAllPages();
}
}
杂项
- 如果对函数功能不明确,先看测试用例(面向测试用例编程)。
- 提交之前先通过编码格式检查。
- 如果有报错:
The autograder failed to execute correctly. Please ensure that your submission is valid. Contact your course staff for help in debugging this issue. Make sure to include a link to this page so that they can help you most effectively.
,可能是代码编写有错误。先将空白(原始)文件上传看是否是网站问题,如果不是再确认代码。 - 每个task依次做完,每做完一个task就上传去测试,后续的上传空白(原始)文件。
标签:15,2021fall,frame,445,id,_.,pages,return,page 来源: https://www.cnblogs.com/pannnn/p/16383441.html