storage/allocator/ssd/ssd_buddy_allocator.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include <cstring> | ||
| 5 | #include <mutex> | ||
| 6 | #include <stdexcept> | ||
| 7 | #include <unordered_set> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "storage/allocator/ssd/ssd_block_allocator.h" | ||
| 11 | #include "storage/io_backend/io_backend.h" | ||
| 12 | |||
| 13 | class SsdBuddyAllocator : public SsdBlockAllocator { | ||
| 14 | public: | ||
| 15 | 642 | SsdBuddyAllocator(IOBackend* backend, | |
| 16 | int min_block_size, | ||
| 17 | int num_levels, | ||
| 18 | uint64_t capacity_bytes, | ||
| 19 | uint64_t base_byte_offset) | ||
| 20 | 1284 | : backend_(backend), | |
| 21 | 642 | min_block_size_(min_block_size), | |
| 22 | 642 | num_levels_(num_levels), | |
| 23 | 642 | total_min_blocks_(capacity_bytes / min_block_size), | |
| 24 | 642 | base_byte_offset_(base_byte_offset) { | |
| 25 |
3/6✓ Branch 0 taken 642 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 642 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 642 times.
|
642 | if (backend_ == nullptr || min_block_size_ <= 0 || num_levels_ <= 0) { |
| 26 | ✗ | throw std::invalid_argument("invalid SsdBuddyAllocator config"); | |
| 27 | } | ||
| 28 |
1/2✓ Branch 1 taken 642 times.
✗ Branch 2 not taken.
|
642 | free_lists_.resize(num_levels_); |
| 29 | 642 | const uint64_t max_block_min_units = uint64_t{1} << (num_levels_ - 1); | |
| 30 |
2/2✓ Branch 0 taken 1758606 times.
✓ Branch 1 taken 642 times.
|
1759248 | for (uint64_t block = 1; block + max_block_min_units <= total_min_blocks_; |
| 31 | 1758606 | block += max_block_min_units) { | |
| 32 |
1/2✓ Branch 2 taken 1758606 times.
✗ Branch 3 not taken.
|
1758606 | free_lists_.back().insert(block); |
| 33 | } | ||
| 34 | 642 | } | |
| 35 | |||
| 36 | 8487342 | uint64_t Alloc(size_t data_size) override { | |
| 37 |
1/2✓ Branch 1 taken 8487342 times.
✗ Branch 2 not taken.
|
8487342 | std::lock_guard<std::mutex> lock(mu_); |
| 38 |
2/2✓ Branch 1 taken 8487340 times.
✓ Branch 2 taken 2 times.
|
8487342 | const int level = LevelFor(data_size); |
| 39 |
1/2✓ Branch 1 taken 8487340 times.
✗ Branch 2 not taken.
|
16974680 | return AllocAtLevel(level); |
| 40 | 8487342 | } | |
| 41 | |||
| 42 | 41834 | void Free(uint64_t block_addr) override { | |
| 43 |
2/4✓ Branch 0 taken 41834 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 41834 times.
|
41834 | if (block_addr == kInvalidHandle || block_addr == 0) { |
| 44 | ✗ | return; | |
| 45 | } | ||
| 46 | 41834 | uint32_t data_size = 0; | |
| 47 | 41834 | uint32_t level = 0; | |
| 48 |
1/2✓ Branch 1 taken 41834 times.
✗ Branch 2 not taken.
|
41834 | ReadHeader(block_addr, data_size, level); |
| 49 |
1/2✓ Branch 1 taken 41834 times.
✗ Branch 2 not taken.
|
41834 | std::lock_guard<std::mutex> lock(mu_); |
| 50 |
1/2✓ Branch 1 taken 41834 times.
✗ Branch 2 not taken.
|
41834 | FreeAtLevel(block_addr, static_cast<int>(level)); |
| 51 | 41834 | } | |
| 52 | |||
| 53 | 98638 | void Write(uint64_t block_addr, const void* data, size_t data_size) override { | |
| 54 | 98638 | const int level = LevelFor(data_size); | |
| 55 | 98638 | WriteWithLevel(block_addr, level, data, data_size); | |
| 56 | 98638 | } | |
| 57 | |||
| 58 | 73936 | size_t Read(uint64_t block_addr, void* out_buf, size_t buf_size) override { | |
| 59 |
1/2✓ Branch 1 taken 73936 times.
✗ Branch 2 not taken.
|
73936 | std::lock_guard<std::mutex> lock(io_mu_); |
| 60 | 73936 | uint32_t data_size = 0; | |
| 61 | 73936 | uint32_t level = 0; | |
| 62 |
1/2✓ Branch 1 taken 73936 times.
✗ Branch 2 not taken.
|
73936 | ReadHeaderUnlocked(block_addr, data_size, level); |
| 63 | 73936 | const uint64_t block_size = static_cast<uint64_t>(min_block_size_) << level; | |
| 64 | 73936 | const PageID_t start = BlockByteOffset(block_addr) / PAGE_SIZE; | |
| 65 | 73936 | const uint64_t page_off = BlockByteOffset(block_addr) % PAGE_SIZE; | |
| 66 | 73936 | const uint64_t pages = (page_off + block_size + PAGE_SIZE - 1) / PAGE_SIZE; | |
| 67 |
1/2✓ Branch 1 taken 73936 times.
✗ Branch 2 not taken.
|
73936 | char* buf = backend_->AllocateBuffer(pages); |
| 68 |
2/4✓ Branch 2 taken 73936 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 73936 times.
✗ Branch 6 not taken.
|
73936 | backend_->BatchReadPages({{start, buf, pages}}); |
| 69 | 73936 | const size_t actual = std::min(buf_size, static_cast<size_t>(data_size)); | |
| 70 | 73936 | std::memcpy(out_buf, buf + page_off + kHeaderSize, actual); | |
| 71 |
1/2✓ Branch 1 taken 73936 times.
✗ Branch 2 not taken.
|
73936 | backend_->FreeBuffer(buf); |
| 72 | 73936 | return actual; | |
| 73 | 73936 | } | |
| 74 | |||
| 75 | 98638 | uint64_t AllocAndWrite(const void* data, size_t data_size) override { | |
| 76 | 98638 | const uint64_t handle = Alloc(data_size); | |
| 77 |
1/2✓ Branch 0 taken 98638 times.
✗ Branch 1 not taken.
|
98638 | if (handle != kInvalidHandle) { |
| 78 | 98638 | Write(handle, data, data_size); | |
| 79 | } | ||
| 80 | 98638 | return handle; | |
| 81 | } | ||
| 82 | |||
| 83 | 95442 | size_t SlotCapacity(uint64_t block_addr) const override { | |
| 84 |
2/4✓ Branch 0 taken 95442 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 95442 times.
|
95442 | if (block_addr == 0 || block_addr == kInvalidHandle) { |
| 85 | ✗ | return 0; | |
| 86 | } | ||
| 87 |
1/2✓ Branch 1 taken 95442 times.
✗ Branch 2 not taken.
|
95442 | std::lock_guard<std::mutex> lock(io_mu_); |
| 88 | 95442 | uint32_t data_size = 0; | |
| 89 | 95442 | uint32_t level = 0; | |
| 90 |
1/2✓ Branch 1 taken 95442 times.
✗ Branch 2 not taken.
|
95442 | const_cast<SsdBuddyAllocator*>(this)->ReadHeaderUnlocked( |
| 91 | block_addr, data_size, level); | ||
| 92 | 95442 | return (static_cast<uint64_t>(min_block_size_) << level) - kHeaderSize; | |
| 93 | 95442 | } | |
| 94 | |||
| 95 | static constexpr int kHeaderSize = 8; | ||
| 96 | |||
| 97 | private: | ||
| 98 | 8585980 | int LevelFor(size_t data_size) const { | |
| 99 | 8585980 | const uint64_t needed = static_cast<uint64_t>(data_size) + kHeaderSize; | |
| 100 |
2/2✓ Branch 0 taken 17171546 times.
✓ Branch 1 taken 2 times.
|
17171548 | for (int level = 0; level < num_levels_; ++level) { |
| 101 |
2/2✓ Branch 0 taken 8585978 times.
✓ Branch 1 taken 8585568 times.
|
17171546 | if ((static_cast<uint64_t>(min_block_size_) << level) >= needed) { |
| 102 | 8585978 | return level; | |
| 103 | } | ||
| 104 | } | ||
| 105 |
1/2✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | throw std::invalid_argument("SsdBuddyAllocator value too large"); |
| 106 | } | ||
| 107 | |||
| 108 | 8487340 | uint64_t AllocAtLevel(int level) { | |
| 109 |
2/2✓ Branch 0 taken 16409958 times.
✓ Branch 1 taken 6 times.
|
16409964 | for (int cur = level; cur < num_levels_; ++cur) { |
| 110 |
2/2✓ Branch 2 taken 7922624 times.
✓ Branch 3 taken 8487334 times.
|
16409958 | if (free_lists_[cur].empty()) { |
| 111 | 7922624 | continue; | |
| 112 | } | ||
| 113 | 8487334 | uint64_t block = *free_lists_[cur].begin(); | |
| 114 |
1/2✓ Branch 2 taken 8487334 times.
✗ Branch 3 not taken.
|
8487334 | free_lists_[cur].erase(block); |
| 115 |
2/2✓ Branch 0 taken 7922602 times.
✓ Branch 1 taken 8487334 times.
|
16409936 | while (cur > level) { |
| 116 | 7922602 | --cur; | |
| 117 | 7922602 | const uint64_t buddy = block + (uint64_t{1} << cur); | |
| 118 |
1/2✓ Branch 2 taken 7922602 times.
✗ Branch 3 not taken.
|
7922602 | free_lists_[cur].insert(buddy); |
| 119 | } | ||
| 120 | 8487334 | return block; | |
| 121 | } | ||
| 122 | 6 | return kInvalidHandle; | |
| 123 | } | ||
| 124 | |||
| 125 | 41834 | void FreeAtLevel(uint64_t block, int level) { | |
| 126 |
1/2✓ Branch 0 taken 41954 times.
✗ Branch 1 not taken.
|
41954 | while (level + 1 < num_levels_) { |
| 127 | 41954 | const uint64_t buddy = block ^ (uint64_t{1} << level); | |
| 128 |
1/2✓ Branch 2 taken 41954 times.
✗ Branch 3 not taken.
|
41954 | auto it = free_lists_[level].find(buddy); |
| 129 |
2/2✓ Branch 3 taken 41834 times.
✓ Branch 4 taken 120 times.
|
41954 | if (it == free_lists_[level].end()) { |
| 130 | 41834 | break; | |
| 131 | } | ||
| 132 |
1/2✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
|
120 | free_lists_[level].erase(it); |
| 133 | 120 | block = std::min(block, buddy); | |
| 134 | 120 | ++level; | |
| 135 | } | ||
| 136 | 41834 | free_lists_[level].insert(block); | |
| 137 | 41834 | } | |
| 138 | |||
| 139 | 457722 | uint64_t BlockByteOffset(uint64_t block_addr) const { | |
| 140 | 457722 | return base_byte_offset_ + block_addr * min_block_size_; | |
| 141 | } | ||
| 142 | |||
| 143 | 98638 | void WriteWithLevel( | |
| 144 | uint64_t block_addr, int level, const void* data, size_t data_size) { | ||
| 145 |
1/2✓ Branch 1 taken 98638 times.
✗ Branch 2 not taken.
|
98638 | std::lock_guard<std::mutex> lock(io_mu_); |
| 146 | 98638 | const uint64_t block_size = static_cast<uint64_t>(min_block_size_) << level; | |
| 147 | 98638 | const uint64_t byte_offset = BlockByteOffset(block_addr); | |
| 148 | 98638 | const PageID_t start = byte_offset / PAGE_SIZE; | |
| 149 | 98638 | const uint64_t page_off = byte_offset % PAGE_SIZE; | |
| 150 | 98638 | const uint64_t pages = (page_off + block_size + PAGE_SIZE - 1) / PAGE_SIZE; | |
| 151 |
1/2✓ Branch 1 taken 98638 times.
✗ Branch 2 not taken.
|
98638 | char* buf = backend_->AllocateBuffer(pages); |
| 152 |
2/4✓ Branch 2 taken 98638 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 98638 times.
✗ Branch 6 not taken.
|
98638 | backend_->BatchReadPages({{start, buf, pages}}); |
| 153 | 98638 | const uint32_t n = static_cast<uint32_t>(data_size); | |
| 154 | 98638 | const uint32_t l = static_cast<uint32_t>(level); | |
| 155 | 98638 | std::memcpy(buf + page_off, &n, sizeof(n)); | |
| 156 | 98638 | std::memcpy(buf + page_off + sizeof(n), &l, sizeof(l)); | |
| 157 | 98638 | std::memcpy(buf + page_off + kHeaderSize, data, data_size); | |
| 158 |
2/4✓ Branch 2 taken 98638 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 98638 times.
✗ Branch 6 not taken.
|
98638 | backend_->BatchWritePages({{start, buf, pages}}); |
| 159 |
1/2✓ Branch 1 taken 98638 times.
✗ Branch 2 not taken.
|
98638 | backend_->FreeBuffer(buf); |
| 160 | 98638 | } | |
| 161 | |||
| 162 | 41834 | void ReadHeader(uint64_t block_addr, uint32_t& data_size, uint32_t& level) { | |
| 163 |
1/2✓ Branch 1 taken 41834 times.
✗ Branch 2 not taken.
|
41834 | std::lock_guard<std::mutex> lock(io_mu_); |
| 164 |
1/2✓ Branch 1 taken 41834 times.
✗ Branch 2 not taken.
|
41834 | ReadHeaderUnlocked(block_addr, data_size, level); |
| 165 | 41834 | } | |
| 166 | |||
| 167 | 211212 | void ReadHeaderUnlocked( | |
| 168 | uint64_t block_addr, uint32_t& data_size, uint32_t& level) { | ||
| 169 | 211212 | const uint64_t byte_offset = BlockByteOffset(block_addr); | |
| 170 | 211212 | const PageID_t start = byte_offset / PAGE_SIZE; | |
| 171 | 211212 | const uint64_t page_off = byte_offset % PAGE_SIZE; | |
| 172 | 211212 | char* buf = backend_->AllocateBuffer(); | |
| 173 | 211212 | backend_->ReadPage(start, buf); | |
| 174 | 211212 | std::memcpy(&data_size, buf + page_off, sizeof(data_size)); | |
| 175 | 211212 | std::memcpy(&level, buf + page_off + sizeof(data_size), sizeof(level)); | |
| 176 | 211212 | backend_->FreeBuffer(buf); | |
| 177 | 211212 | } | |
| 178 | |||
| 179 | IOBackend* backend_; | ||
| 180 | const int min_block_size_; | ||
| 181 | const int num_levels_; | ||
| 182 | const uint64_t total_min_blocks_; | ||
| 183 | const uint64_t base_byte_offset_; | ||
| 184 | std::mutex mu_; | ||
| 185 | mutable std::mutex io_mu_; | ||
| 186 | std::vector<std::unordered_set<uint64_t>> free_lists_; | ||
| 187 | }; | ||
| 188 | |||
| 189 | FACTORY_REGISTER( | ||
| 190 | SsdBlockAllocator, | ||
| 191 | SSD_BUDDY, | ||
| 192 | SsdBuddyAllocator, | ||
| 193 | IOBackend*, | ||
| 194 | int, | ||
| 195 | int, | ||
| 196 | uint64_t, | ||
| 197 | uint64_t); | ||
| 198 |