|
| 1 | +#include "common/utils.h" |
| 2 | +#include "storage/block_manager.h" |
| 3 | +#include "storage/free_space_manager.h" |
| 4 | + |
| 5 | +namespace kuzu::storage { |
| 6 | +static FreeSpaceManager::sorted_free_list_t& getFreeList( |
| 7 | + std::vector<FreeSpaceManager::sorted_free_list_t>& freeLists, common::idx_t level) { |
| 8 | + if (level >= freeLists.size()) { |
| 9 | + freeLists.resize(level + 1, |
| 10 | + FreeSpaceManager::sorted_free_list_t{&FreeSpaceManager::entryCmp}); |
| 11 | + } |
| 12 | + return freeLists[level]; |
| 13 | +} |
| 14 | + |
| 15 | +FreeSpaceManager::FreeSpaceManager() : freeLists{} {}; |
| 16 | + |
| 17 | +bool FreeSpaceManager::entryCmp(const BlockEntry& a, const BlockEntry& b) { |
| 18 | + return a.numPages == b.numPages ? a.startPageIdx < b.startPageIdx : a.numPages < b.numPages; |
| 19 | +} |
| 20 | + |
| 21 | +void FreeSpaceManager::addFreeChunk(BlockEntry entry) { |
| 22 | + const auto curLevel = common::countBits(entry.numPages) - |
| 23 | + common::CountZeros<decltype(entry.numPages)>::Leading(entry.numPages) - 1; |
| 24 | + getFreeList(freeLists, curLevel).insert(entry); |
| 25 | +} |
| 26 | + |
| 27 | +void FreeSpaceManager::addUncheckpointedFreeChunk(BlockEntry entry) { |
| 28 | + uncheckpointedFreeChunks.push_back(entry); |
| 29 | +} |
| 30 | + |
| 31 | +// This also removes the chunk from the free space manager |
| 32 | +std::optional<BlockEntry> FreeSpaceManager::getFreeChunk(common::page_idx_t numPages) { |
| 33 | + if (numPages > 0) { |
| 34 | + // 0b10 -> start at level 1 |
| 35 | + // 0b11 -> start at level 1 |
| 36 | + auto levelToSearch = common::countBits(numPages) - |
| 37 | + common::CountZeros<decltype(numPages)>::Leading(numPages) - 1; |
| 38 | + for (; levelToSearch < freeLists.size(); ++levelToSearch) { |
| 39 | + auto& curList = freeLists[levelToSearch]; |
| 40 | + auto entryIt = curList.lower_bound(BlockEntry{0, numPages}); |
| 41 | + if (entryIt != curList.end()) { |
| 42 | + auto entry = *entryIt; |
| 43 | + curList.erase(entry); |
| 44 | + return breakUpChunk(entry, numPages); |
| 45 | + } |
| 46 | + } |
| 47 | + } |
| 48 | + return std::nullopt; |
| 49 | +} |
| 50 | + |
| 51 | +BlockEntry FreeSpaceManager::breakUpChunk(BlockEntry chunk, common::page_idx_t numRequiredPages) { |
| 52 | + KU_ASSERT(chunk.numPages >= numRequiredPages); |
| 53 | + BlockEntry ret{chunk.startPageIdx, numRequiredPages}; |
| 54 | + if (numRequiredPages < chunk.numPages) { |
| 55 | + BlockEntry remainingEntry{chunk.startPageIdx + numRequiredPages, |
| 56 | + chunk.numPages - numRequiredPages}; |
| 57 | + addFreeChunk(remainingEntry); |
| 58 | + } |
| 59 | + return ret; |
| 60 | +} |
| 61 | + |
| 62 | +void FreeSpaceManager::serialize(common::Serializer&) {} |
| 63 | + |
| 64 | +std::unique_ptr<FreeSpaceManager> FreeSpaceManager::deserialize(common::Deserializer&) { |
| 65 | + return std::make_unique<FreeSpaceManager>(); |
| 66 | +} |
| 67 | + |
| 68 | +void FreeSpaceManager::finalizeCheckpoint() { |
| 69 | + for (auto uncheckpointedEntry : uncheckpointedFreeChunks) { |
| 70 | + addFreeChunk(uncheckpointedEntry); |
| 71 | + } |
| 72 | + uncheckpointedFreeChunks.clear(); |
| 73 | + |
| 74 | + combineAdjacentChunks(); |
| 75 | +} |
| 76 | + |
| 77 | +void FreeSpaceManager::combineAdjacentChunks() { |
| 78 | + std::vector<BlockEntry> allEntries; |
| 79 | + for (const auto& freeList : freeLists) { |
| 80 | + allEntries.insert(allEntries.end(), freeList.begin(), freeList.end()); |
| 81 | + } |
| 82 | + if (allEntries.empty()) { |
| 83 | + return; |
| 84 | + } |
| 85 | + |
| 86 | + freeLists.clear(); |
| 87 | + std::sort(allEntries.begin(), allEntries.end(), [](const auto& entryA, const auto& entryB) { |
| 88 | + return entryA.startPageIdx < entryB.startPageIdx; |
| 89 | + }); |
| 90 | + |
| 91 | + BlockEntry prevEntry = allEntries[0]; |
| 92 | + for (common::row_idx_t i = 1; i < allEntries.size(); ++i) { |
| 93 | + const auto& entry = allEntries[i]; |
| 94 | + if (prevEntry.startPageIdx + prevEntry.numPages == entry.startPageIdx) { |
| 95 | + prevEntry.numPages += entry.numPages; |
| 96 | + } else { |
| 97 | + addFreeChunk(prevEntry); |
| 98 | + prevEntry = entry; |
| 99 | + } |
| 100 | + } |
| 101 | + addFreeChunk(prevEntry); |
| 102 | +} |
| 103 | + |
| 104 | +common::row_idx_t FreeSpaceManager::getNumEntries() const { |
| 105 | + common::row_idx_t ret = 0; |
| 106 | + for (const auto& freeList : freeLists) { |
| 107 | + ret += freeList.size(); |
| 108 | + } |
| 109 | + return ret; |
| 110 | +} |
| 111 | + |
| 112 | +static std::pair<common::idx_t, common::row_idx_t> getStartFreeEntry(common::row_idx_t startOffset, |
| 113 | + const std::vector<FreeSpaceManager::sorted_free_list_t>& freeLists) { |
| 114 | + size_t freeListIdx = 0; |
| 115 | + common::row_idx_t curOffset = 0; |
| 116 | + while (startOffset - curOffset >= freeLists[freeListIdx].size()) { |
| 117 | + KU_ASSERT(freeListIdx < freeLists.size()); |
| 118 | + curOffset += freeLists[freeListIdx].size(); |
| 119 | + ++freeListIdx; |
| 120 | + } |
| 121 | + return {freeListIdx, startOffset - curOffset}; |
| 122 | +} |
| 123 | + |
| 124 | +std::vector<BlockEntry> FreeSpaceManager::getEntries(common::row_idx_t startOffset, |
| 125 | + common::row_idx_t endOffset) const { |
| 126 | + KU_ASSERT(endOffset >= startOffset); |
| 127 | + std::vector<BlockEntry> ret; |
| 128 | + auto [freeListIdx, idxInList] = getStartFreeEntry(startOffset, freeLists); |
| 129 | + for (; freeListIdx < freeLists.size(); ++freeListIdx) { |
| 130 | + auto it = freeLists[freeListIdx].begin(); |
| 131 | + // TODO(Royi) optimize this |
| 132 | + std::advance(it, idxInList); |
| 133 | + for (; it != freeLists[freeListIdx].end(); ++it) { |
| 134 | + if (ret.size() >= endOffset - startOffset) { |
| 135 | + return ret; |
| 136 | + } |
| 137 | + ret.push_back(*it); |
| 138 | + } |
| 139 | + idxInList = 0; |
| 140 | + } |
| 141 | + return ret; |
| 142 | +} |
| 143 | + |
| 144 | +} // namespace kuzu::storage |
0 commit comments