Skip to content

Commit c3a3f3c

Browse files
authored
P1 F2025 Rewrite (#836)
- Add ARC replacer - Change disk scheduler interfaces to take a vector - Bump page size to 8kb
1 parent ad2b709 commit c3a3f3c

22 files changed

+565
-66
lines changed

.github/workflows/cmake.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,6 @@ jobs:
4040
format: "/opt/homebrew/opt/llvm@15/bin/clang-format",
4141
tidy: "/opt/homebrew/opt/llvm@15/bin/clang-tidy",
4242
}
43-
- {
44-
name: "macOS 15 (Sequoia) AppleClang",
45-
os: macos-15,
46-
format: "/opt/homebrew/opt/llvm@15/bin/clang-format",
47-
tidy: "/opt/homebrew/opt/llvm@15/bin/clang-tidy",
48-
}
4943
steps:
5044
- uses: actions/checkout@v2
5145

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ add_custom_target(submit-p0
274274
)
275275

276276
set(P1_FILES
277-
"src/include/buffer/lru_k_replacer.h"
278-
"src/buffer/lru_k_replacer.cpp"
277+
"src/include/buffer/arc_replacer.h"
278+
"src/buffer/arc_replacer.cpp"
279279
"src/include/buffer/buffer_pool_manager.h"
280280
"src/buffer/buffer_pool_manager.cpp"
281281
"src/include/storage/disk/disk_scheduler.h"

src/buffer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_library(
22
bustub_buffer
33
OBJECT
44
buffer_pool_manager.cpp
5+
arc_replacer.cpp
56
clock_replacer.cpp
67
lru_replacer.cpp
78
lru_k_replacer.cpp)

src/buffer/arc_replacer.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// :bustub-keep-private:
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// BusTub
5+
//
6+
// arc_replacer.cpp
7+
//
8+
// Identification: src/buffer/arc_replacer.cpp
9+
//
10+
// Copyright (c) 2015-2025, Carnegie Mellon University Database Group
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "buffer/arc_replacer.h"
15+
#include <optional>
16+
#include "common/config.h"
17+
18+
namespace bustub {
19+
20+
/**
21+
*
22+
* TODO(P1): Add implementation
23+
*
24+
* @brief a new ArcReplacer, with lists initialized to be empty and target size to 0
25+
* @param num_frames the maximum number of frames the ArcReplacer will be required to cache
26+
*/
27+
ArcReplacer::ArcReplacer(size_t num_frames) : replacer_size_(num_frames) {}
28+
29+
/**
30+
* TODO(P1): Add implementation
31+
*
32+
* @brief Performs the Replace operation as described by the writeup
33+
* that evicts from either mfu_ or mru_ into its corresponding ghost list
34+
* according to balancing policy.
35+
*
36+
* If you wish to refer to the original ARC paper, please note that there are
37+
* two changes in our implementation:
38+
* 1. When the size of mru_ equals the target size, we don't check
39+
* the last access as the paper did when deciding which list to evict from.
40+
* This is fine since the original decision is stated to be arbitrary.
41+
* 2. Entries that are not evictable are skipped. If all entries from the desired side
42+
* (mru_ / mfu_) are pinned, we instead try victimize the other side (mfu_ / mru_),
43+
* and move it to its corresponding ghost list (mfu_ghost_ / mru_ghost_).
44+
*
45+
* @return frame id of the evicted frame, or std::nullopt if cannot evict
46+
*/
47+
auto ArcReplacer::Evict() -> std::optional<frame_id_t> { return std::nullopt; }
48+
49+
/**
50+
* TODO(P1): Add implementation
51+
*
52+
* @brief Record access to a frame, adjusting ARC bookkeeping accordingly
53+
* by bring the accessed page to the front of mfu_ if it exists in any of the lists
54+
* or the front of mru_ if it does not.
55+
*
56+
* Performs the operations EXCEPT REPLACE described in original paper, which is
57+
* handled by `Evict()`.
58+
*
59+
* Consider the following four cases, handle accordingly:
60+
* 1. Access hits mru_ or mfu_
61+
* 2/3. Access hits mru_ghost_ / mfu_ghost_
62+
* 4. Access misses all the lists
63+
*
64+
* This routine performs all changes to the four lists as preperation
65+
* for `Evict()` to simply find and evict a victim into ghost lists.
66+
*
67+
* Note that frame_id is used as identifier for alive pages and
68+
* page_id is used as identifier for the ghost pages, since page_id is
69+
* the unique identifier to the page after it's dead.
70+
* Using page_id for alive pages should be the same since it's one to one mapping,
71+
* but using frame_id is slightly more intuitive.
72+
*
73+
* @param frame_id id of frame that received a new access.
74+
* @param page_id id of page that is mapped to the frame.
75+
* @param access_type type of access that was received. This parameter is only needed for
76+
* leaderboard tests.
77+
*/
78+
void ArcReplacer::RecordAccess(frame_id_t frame_id, page_id_t page_id, [[maybe_unused]] AccessType access_type) {}
79+
80+
/**
81+
* TODO(P1): Add implementation
82+
*
83+
* @brief Toggle whether a frame is evictable or non-evictable. This function also
84+
* controls replacer's size. Note that size is equal to number of evictable entries.
85+
*
86+
* If a frame was previously evictable and is to be set to non-evictable, then size should
87+
* decrement. If a frame was previously non-evictable and is to be set to evictable,
88+
* then size should increment.
89+
*
90+
* If frame id is invalid, throw an exception or abort the process.
91+
*
92+
* For other scenarios, this function should terminate without modifying anything.
93+
*
94+
* @param frame_id id of frame whose 'evictable' status will be modified
95+
* @param set_evictable whether the given frame is evictable or not
96+
*/
97+
void ArcReplacer::SetEvictable(frame_id_t frame_id, bool set_evictable) {}
98+
99+
/**
100+
* TODO(P1): Add implementation
101+
*
102+
* @brief Remove an evictable frame from replacer.
103+
* This function should also decrement replacer's size if removal is successful.
104+
*
105+
* Note that this is different from evicting a frame, which always remove the frame
106+
* decided by the ARC algorithm.
107+
*
108+
* If Remove is called on a non-evictable frame, throw an exception or abort the
109+
* process.
110+
*
111+
* If specified frame is not found, directly return from this function.
112+
*
113+
* @param frame_id id of frame to be removed
114+
*/
115+
void ArcReplacer::Remove(frame_id_t frame_id) {}
116+
117+
/**
118+
* TODO(P1): Add implementation
119+
*
120+
* @brief Return replacer's size, which tracks the number of evictable frames.
121+
*
122+
* @return size_t
123+
*/
124+
auto ArcReplacer::Size() -> size_t { return 0; }
125+
126+
} // namespace bustub

src/buffer/buffer_pool_manager.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "buffer/buffer_pool_manager.h"
14+
#include "buffer/arc_replacer.h"
15+
#include "common/config.h"
16+
#include "common/macros.h"
1417

1518
namespace bustub {
1619

@@ -63,15 +66,13 @@ void FrameHeader::Reset() {
6366
*
6467
* @param num_frames The size of the buffer pool.
6568
* @param disk_manager The disk manager.
66-
* @param k_dist The backward k-distance for the LRU-K replacer.
6769
* @param log_manager The log manager. Please ignore this for P1.
6870
*/
69-
BufferPoolManager::BufferPoolManager(size_t num_frames, DiskManager *disk_manager, size_t k_dist,
70-
LogManager *log_manager)
71+
BufferPoolManager::BufferPoolManager(size_t num_frames, DiskManager *disk_manager, LogManager *log_manager)
7172
: num_frames_(num_frames),
7273
next_page_id_(0),
7374
bpm_latch_(std::make_shared<std::mutex>()),
74-
replacer_(std::make_shared<LRUKReplacer>(num_frames, k_dist)),
75+
replacer_(std::make_shared<ArcReplacer>(num_frames)),
7576
disk_scheduler_(std::make_shared<DiskScheduler>(disk_manager)),
7677
log_manager_(log_manager) {
7778
// Not strictly necessary...
@@ -342,11 +343,13 @@ void BufferPoolManager::FlushAllPages() { UNIMPLEMENTED("TODO(P1): Add implement
342343
* # Implementation
343344
*
344345
* We will use this function to test if your buffer pool manager is managing pin counts correctly. Since the
345-
* `pin_count_` field in `FrameHeader` is an [atomic type](https://en.cppreference.com/w/cpp/atomic/atomic),
346-
* you do not need to take the latch on the frame that holds the
346+
* `pin_count_` field in `FrameHeader` is an atomic type, you do not need to take the latch on the frame that holds the
347347
* page we want to look at. Instead, you can simply use an atomic `load` to safely load the value stored. You will still
348348
* need to take the buffer pool latch, however.
349349
*
350+
* Again, if you are unfamiliar with atomic types, see the official C++ docs
351+
* [here](https://en.cppreference.com/w/cpp/atomic/atomic).
352+
*
350353
* TODO(P1): Add implementation
351354
*
352355
* @param page_id The page ID of the page we want to get the pin count of.

src/common/bustub_instance.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ BusTubInstance::BusTubInstance(const std::filesystem::path &db_file_name, size_t
7474
// We need more frames for GenerateTestTable to work. Therefore, we use 128 instead of the default
7575
// buffer pool size specified in `config.h`.
7676
try {
77-
buffer_pool_manager_ =
78-
std::make_unique<BufferPoolManager>(bpm_size, disk_manager_.get(), LRUK_REPLACER_K, log_manager_.get());
77+
buffer_pool_manager_ = std::make_unique<BufferPoolManager>(bpm_size, disk_manager_.get(), log_manager_.get());
7978
} catch (NotImplementedException &e) {
8079
std::cerr << "BufferPoolManager is not implemented, only mock tables are supported." << std::endl;
8180
buffer_pool_manager_ = nullptr;
@@ -127,8 +126,7 @@ BusTubInstance::BusTubInstance(size_t bpm_size) {
127126
// We need more frames for GenerateTestTable to work. Therefore, we use 128 instead of the default
128127
// buffer pool size specified in `config.h`.
129128
try {
130-
buffer_pool_manager_ =
131-
std::make_unique<BufferPoolManager>(bpm_size, disk_manager_.get(), LRUK_REPLACER_K, log_manager_.get());
129+
buffer_pool_manager_ = std::make_unique<BufferPoolManager>(bpm_size, disk_manager_.get(), log_manager_.get());
132130
} catch (NotImplementedException &e) {
133131
std::cerr << "BufferPoolManager is not implemented, only mock tables are supported." << std::endl;
134132
buffer_pool_manager_ = nullptr;

src/include/buffer/arc_replacer.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// BusTub
5+
//
6+
// arc_replacer.h
7+
//
8+
// Identification: src/include/buffer/arc_replacer.h
9+
//
10+
// Copyright (c) 2015-2025, Carnegie Mellon University Database Group
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#pragma once
15+
16+
#include <list>
17+
#include <memory>
18+
#include <mutex> // NOLINT
19+
#include <optional>
20+
#include <unordered_map>
21+
22+
#include "common/config.h"
23+
#include "common/macros.h"
24+
25+
namespace bustub {
26+
27+
enum class AccessType { Unknown = 0, Lookup, Scan, Index };
28+
29+
enum class ArcStatus { MRU, MFU, MRU_GHOST, MFU_GHOST };
30+
31+
// TODO(student): You can modify or remove this struct as you like.
32+
struct FrameStatus {
33+
page_id_t page_id_;
34+
frame_id_t frame_id_;
35+
bool evictable_;
36+
ArcStatus arc_status_;
37+
FrameStatus(page_id_t pid, frame_id_t fid, bool ev, ArcStatus st)
38+
: page_id_(pid), frame_id_(fid), evictable_(ev), arc_status_(st) {}
39+
};
40+
41+
/**
42+
* ArcReplacer implements the ARC replacement policy.
43+
*/
44+
class ArcReplacer {
45+
public:
46+
explicit ArcReplacer(size_t num_frames);
47+
48+
DISALLOW_COPY_AND_MOVE(ArcReplacer);
49+
50+
/**
51+
* TODO(P1): Add implementation
52+
*
53+
* @brief Destroys the LRUReplacer.
54+
*/
55+
~ArcReplacer() = default;
56+
57+
auto Evict() -> std::optional<frame_id_t>;
58+
void RecordAccess(frame_id_t frame_id, page_id_t page_id, AccessType access_type = AccessType::Unknown);
59+
void SetEvictable(frame_id_t frame_id, bool set_evictable);
60+
void Remove(frame_id_t frame_id);
61+
auto Size() -> size_t;
62+
63+
private:
64+
// TODO(student): implement me! You can replace or remove these member variables as you like.
65+
std::list<frame_id_t> mru_;
66+
std::list<frame_id_t> mfu_;
67+
std::list<page_id_t> mru_ghost_;
68+
std::list<page_id_t> mfu_ghost_;
69+
70+
/* record entries in mru_ and mfu_
71+
* this uses frame_id_t to guarantee no duplicate records for the same
72+
* frame when they are alive */
73+
std::unordered_map<frame_id_t, std::shared_ptr<FrameStatus>> alive_map_;
74+
/* record entries in mru_ghost_ and mfu_ghost_
75+
* this uses page_id_t but not frame_id_t because page_id is the unique
76+
* identifier in ghost lists */
77+
std::unordered_map<page_id_t, std::shared_ptr<FrameStatus>> ghost_map_;
78+
79+
/* alive, evictable entries count */
80+
[[maybe_unused]] size_t curr_size_{0};
81+
/* p as in original paper */
82+
[[maybe_unused]] size_t mru_target_size_{0};
83+
/* c as in original paper */
84+
[[maybe_unused]] size_t replacer_size_;
85+
std::mutex latch_;
86+
87+
// TODO(student): You can add member variables / functions as you like.
88+
};
89+
90+
} // namespace bustub

src/include/buffer/buffer_pool_manager.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <unordered_map>
1919
#include <vector>
2020

21-
#include "buffer/lru_k_replacer.h"
21+
#include "buffer/arc_replacer.h"
2222
#include "common/config.h"
2323
#include "recovery/log_manager.h"
2424
#include "storage/disk/disk_scheduler.h"
@@ -105,12 +105,11 @@ class FrameHeader {
105105
* faster access, and evicting unused or cold pages back out to storage.
106106
*
107107
* Make sure you read the writeup in its entirety before attempting to implement the buffer pool manager. You also need
108-
* to have completed the implementation of both the `LRUKReplacer` and `DiskManager` classes.
108+
* to have completed the implementation of both the `ArcReplacer` and `DiskManager` classes.
109109
*/
110110
class BufferPoolManager {
111111
public:
112-
BufferPoolManager(size_t num_frames, DiskManager *disk_manager, size_t k_dist = LRUK_REPLACER_K,
113-
LogManager *log_manager = nullptr);
112+
BufferPoolManager(size_t num_frames, DiskManager *disk_manager, LogManager *log_manager = nullptr);
114113
~BufferPoolManager();
115114

116115
auto Size() const -> size_t;
@@ -151,7 +150,7 @@ class BufferPoolManager {
151150
std::list<frame_id_t> free_frames_;
152151

153152
/** @brief The replacer to find unpinned / candidate pages for eviction. */
154-
std::shared_ptr<LRUKReplacer> replacer_;
153+
std::shared_ptr<ArcReplacer> replacer_;
155154

156155
/** @brief A pointer to the disk scheduler. Shared with the page guards for flushing. */
157156
std::shared_ptr<DiskScheduler> disk_scheduler_;

src/include/buffer/lru_k_replacer.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919
#include <unordered_map>
2020
#include <vector>
2121

22+
#include "buffer/arc_replacer.h"
2223
#include "common/config.h"
2324
#include "common/macros.h"
2425

2526
namespace bustub {
2627

27-
enum class AccessType { Unknown = 0, Lookup, Scan, Index };
28-
2928
class LRUKNode {
3029
private:
3130
/** History of last seen K timestamps of this page. Least recent timestamp stored in front. */

src/include/common/config.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ static constexpr int INVALID_PAGE_ID = -1; // invalid page id
3535
static constexpr int INVALID_TXN_ID = -1; // invalid transaction id
3636
static constexpr int INVALID_LSN = -1; // invalid log sequence number
3737

38-
static constexpr int BUSTUB_PAGE_SIZE = 4096; // size of a data page in byte
38+
static constexpr int BUSTUB_PAGE_SIZE = 8192; // size of a data page in byte
3939
static constexpr int BUFFER_POOL_SIZE = 128; // size of buffer pool
4040
static constexpr int DEFAULT_DB_IO_SIZE = 16; // starting size of file on disk
4141
static constexpr int LOG_BUFFER_SIZE = ((BUFFER_POOL_SIZE + 1) * BUSTUB_PAGE_SIZE); // size of a log buffer in byte

0 commit comments

Comments
 (0)