CMU15445 FALL2022 Project #1 - Buffer Pool
Task #1 - Extendible Hash Table
Find(K, V)
- 通过K利用
IndexOf
得到索引,取对应桶中Find(K,V)
Insert(K, V)
-
通过K利用
IndexOf
得到索引,获取对应桶 -
如果桶满了
-
如果桶的
Depth
与GlobalDepthInternal
相等了-
GlobalDepthInternal
++ -
将dir_增加一倍,并将第i+原
capacity
的dir_
指向第i的dir_
-
新建两个深度为原桶
Depth
+1的桶,按照mask将原桶中的内容插入新建的桶-
auto mask = 1 << target_bucket->GetDepth(); for (const auto &item : target_bucket->GetItems()) { size_t hash_key = std::hash<K>()(item.first); if ((hash_key & mask) == 0U) { bucket0->Insert(item.first, item.second); } else { bucket1->Insert(item.first, item.second); } }
-
-
遍历
dir_
,按照mask将指向原来桶的跟新到新建的两个桶-
auto capacity = dir_.size(); for (size_t i = 0; i < capacity; i++) { if (dir_[i] == target_bucket) { if ((i & mask) == 0U) { dir_[i] = bucket0; } else { dir_[i] = bucket1; } } }
-
-
-
-
取对应桶中
Insert(K,V)
Remove(K)
-
通过K利用
IndexOf
得到索引,取对应桶中Remove(K,V)
-
注意给新增加的
target_bucket
设置dir_
的时候,不一定只有之前的两个,要遍历检测一边 -
for (size_t i = 0; i < capacity; i++) { if (dir_[i] == target_bucket) { if ((i & mask) == 0U) { dir_[i] = bucket0; } else { dir_[i] = bucket1; } } }
- 将新增的
bucke
配对时,要涉及到所有之前配对tartge bucket
的dir_
- 将新增的
Task #2 - LRU-K Replacement Policy
[[maybe_unused]] size_t current_timestamp_{0};
size_t curr_size_{0};
size_t replacer_size_;
size_t k_;
std::mutex latch_;
std::unordered_map<frame_id_t, size_t> access_count_;
std::list<frame_id_t> history_list_; // 用于存放可以剔除的帧,即访问次数未达到 k
std::unordered_map<frame_id_t, std::list<frame_id_t>::iterator> history_map_; // 用于存放可以剔除的帧的id和iter映射,方便快速查找
std::list<frame_id_t> cache_list_; // 用于存放不可以被剔除的帧,即访问次数已达到k,头插,尾部取
std::unordered_map<frame_id_t, std::list<frame_id_t>::iterator> cache_map_; // 用于存放不可以被剔除的帧的id和iter映射,方便快速查找
std::unordered_map<frame_id_t, bool> is_evictable_; // 用于是否被evict
Evict(frame_id_t)*
- 遍历
history_list_
:- 如果存在且
is_evictable_
为true
:- 将
access_count_
设为0
。 - 从
history_list_
和history_map_
中删除对应项。 - 将
curr_size_
减1
,并将is_evictable_
设为false
。 - 将
frame_id
设为当前帧,返回true
。
- 将
- 如果存在且
- 从 尾部向前 遍历
cache_list_
:- 如果存在且
is_evictable_
为 true:- 将
access_count_
设为0
。 - 从
cache_list_
和cache_map_
中删除对应项。 - 将
curr_size_
减1
,并将is_evictable_
设为false
。 - 将
frame_id
设为当前帧,返回true
。
- 将
- 如果存在且
- 返回
false
。
RecordAccess(frame_id_t)
- 首先
access_count_
++ - 如果
access_count_
等于阈值k_history_list_
、history_map_
删除对应项cache_list_
将该项插入头部、cache_map_
中也插入对应项
- 如果
access_count_
大于阈值k_cache_list_
、cache_map_
中删除对应项cache_list_
将该项插入头部、cache_map_
中也插入对应项
- 如果
access_count_
小于阈值k_history_list_
将该项插入头部、history_map_
中也插入对应项
Remove(frame_id_t)
-
如果
access_count_
小于k_history_list_
、history_map_
删除对应项
-
如果
access_count_
大于等于k_cache_list_
、cache_map_
删除对应项
-
size_
–,access_count_
设为0,is_evictable_
设为false
SetEvictable(frame_id_t, bool set_evictable)
- 如果
is_evictable_
为false
且set_evictable
为true
size
++
- 如果
is_evictable_
为true
且set_evictable
为false
size
–
is_evictable_
设置为set_evictable
Task #3 - Buffer Pool Manager Instance
FetchPgImp(page_id)
-
检查页面是否已经在缓冲池中:
-
使用
page_table_->Find(page_id, frame_id)
检查指定的page_id
是否已经在页面表(page_table_
)中 -
如果找到:
-
增加该页面的固定计数(
pin_count_
-
记录该页面被访问了(
replacer_->RecordAccess(frame_id)
) -
设置该页面为不可逐出状态(
replacer_->SetEvictable(frame_id, false)
)返回该页面的指针
-
-
-
检查是否有空闲页面:
- 遍历缓冲池中的所有页面,检查是否存在固定计数为零的页面(即空闲页面)
- 如果找到空闲页面,将
is_free_page
设置为true
-
如果没有空闲页面:
- 如果没有空闲页面,直接返回
nullptr
,表示无法获取页面
- 如果没有空闲页面,直接返回
-
寻找或逐出页面:
- 如果有空闲页面,首先检查空闲列表是否为空:
- 如果不为空,从空闲列表中弹出一个帧 ID(
frame_id
) - 如果为空,则使用替换策略(
replacer_->Evict(&frame_id)
)逐出一个页面 - 逐出页面时,如果页面是脏的(
IsDirty()
),则将其写回磁盘(disk_manager_->WritePage
),并清除脏标记 - 清除逐出页面的内存(
pages_[frame_id].ResetMemory()
) - 从页面表中移除逐出的页面
- 如果不为空,从空闲列表中弹出一个帧 ID(
- 如果有空闲页面,首先检查空闲列表是否为空:
-
加载新页面:
- 将新页面插入页面表(
page_table_->Insert(page_id, frame_id)
) - 设置新页面的 ID 和固定计数
- 从磁盘读取页面内容(
disk_manager_->ReadPage(page_id, pages_[frame_id].GetData())
) - 记录新页面的访问(
replacer_->RecordAccess(frame_id)
) - 设置新页面为不可逐出状态(
replacer_->SetEvictable(frame_id, false)
)
- 将新页面插入页面表(
-
返回页面指针:
- 返回加载或找到的页面的指针(
&pages_[frame_id]
)
- 返回加载或找到的页面的指针(
UnpinPgImp(page_id, is_dirty)
- 加锁保护:
- 使用
std::scoped_lock<std::mutex>
对latch_
进行加锁,以确保线程安全
- 使用
- 查找页面:
- 使用
page_table_->Find(page_id, frame_id)
查找页面表(page_table_
)中的指定页面 ID (page_id
),获取对应的帧 ID (frame_id
) - 如果页面表中没有找到对应的页面 ID,返回
false
- 使用
- 检查页面固定计数:
- 如果找到的页面固定计数(
pin_count_
)小于等于零,返回false
- 如果找到的页面固定计数(
- 处理脏页:
- 如果
is_dirty
为true
,将页面标记为脏页
- 如果
- 减少页面的固定计数:
- 将页面的固定计数减一
- 设置页面为可逐出状态:
- 如果页面的固定计数减为零,将该页面设置为可逐出状态
FlushPgImp(page_id)
- 无效页面 ID 检查:
- 如果传入的
page_id
是无效的,直接返回false
,表示刷新操作失败
- 如果传入的
- 查找页面:
- 使用
page_table_->Find(page_id, frame_id)
在页面表(page_table_
)中查找指定的页面 ID (page_id
),获取对应的帧 ID (frame_id
) - 如果页面表中没有找到对应的页面 ID,返回
false
,表示页面不在缓冲池中,刷新操作失败
- 使用
- 写入磁盘:
- 调用
disk_manager_->WritePage(page_id, pages_[frame_id].data_)
将页面数据写入磁盘。这里使用的是页面 ID 和页面数
- 调用
NewPgImp(page_id)
- 检查是否有空闲页面:
- 遍历缓冲池中的所有页面,检查是否存在固定计数为零的页面(即空闲页面)
- 如果找到空闲页面,将
is_free_page
设置为true
- 如果没有空闲页面:
- 如果没有空闲页面,返回
nullptr
,表示无法分配新页面
- 如果没有空闲页面,返回
- 分配新页面 ID:
- 调用
AllocatePage()
分配一个新的页面 ID,并将其存储在*page_id
中
- 调用
- 寻找或逐出页面:
- 检查空闲列表是否为空:
- 如果不为空,从空闲列表中弹出一个帧 ID(
frame_id
) - 如果为空,则使用替换策略(
replacer_->Evict(&frame_id)
)逐出一个页面 - 逐出页面时,如果页面是脏的(
IsDirty()
),则将其写回磁盘(disk_manager_->WritePage
),并清除脏标记 - 清除逐出页面的内存
- 从页面表中移除逐出的页面
- 如果不为空,从空闲列表中弹出一个帧 ID(
- 检查空闲列表是否为空:
- 插入新页面:
- 将新页面插入页面表
- 设置新页面的 ID 和固定计数
- 更新替换器状态:
- 记录新页面的访问
- 设置新页面为不可逐出状态
DeletePgImp(page_id)
- 查找页面:
- 使用
page_table_->Find(page_id, frame_id)
在页面表(page_table_
)中查找指定的页面 ID (page_id
),获取对应的帧 ID (frame_id
) - 如果页面表中没有找到对应的页面 ID,返回
true
,表示页面不存在,因此也不需要删除
- 使用
- 检查页面固定计数:
- 如果找到的页面固定计数(
pin_count_
)大于零,返回false
,表示页面仍在使用,无法删除
- 如果找到的页面固定计数(
- 从替换器中移除页面:
- 调用
replacer_->Remove(frame_id)
将页面从替换器中移除
- 调用
- 重置页面内存:
- 调用
pages_[frame_id].ResetMemory()
清除页面的内存 - 将页面 ID 设置为无效值
- 将页面的固定计数重置为零
- 将页面的脏标记清除)
- 调用
- 更新页面表和空闲列表:
- 从页面表中移除该页面
- 将帧 ID 添加到空闲列表中
- 释放页面 ID:
- 调用
DeallocatePage(page_id)
释放页面 ID。
- 调用
FlushAllPagesImpl()
- 循环
FlushPgImp
所有的page
注意事项
- 注意
new
和fetch
后要设置evit
为false
- 在
pin--等于0
的时候设置evit
为true
‘
最后放个线上测试图