diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index d2840a2..42c677e 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -24,7 +24,7 @@ jobs: # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: os: [ubuntu-latest, windows-latest] - build_type: [Release] + build_type: [Debug] c_compiler: [gcc, clang, cl] include: - os: windows-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index ecc4a9b..417d49f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) add_subdirectory(main) add_subdirectory(LRU) add_subdirectory(LFU) +add_subdirectory(perfect) include(CTest) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index bf48b14..16f11d0 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -14,4 +14,5 @@ target_link_libraries(main PRIVATE LRU LFU + perfect ) diff --git a/main/input_data.dat b/main/input_data.dat new file mode 100644 index 0000000..e8b388a --- /dev/null +++ b/main/input_data.dat @@ -0,0 +1 @@ +2 6 3 3 1 2 1 2 diff --git a/main/main.cpp b/main/main.cpp index 049875d..5400a7a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,5 +1,6 @@ #include "LFU.hpp" #include "LRU.hpp" +#include "perfect.hpp" #include #include #include @@ -8,22 +9,30 @@ int slow_get_page(int key) { return key; } int main() { - std::map cache_hits; size_t sz; int nelts; std::cin >> sz >> nelts; + std::vector keys; + for (int i = 0; i < nelts; ++i) { + int key; + std::cin >> key; + assert(std::cin.good()); + keys.push_back(key); + } + cache::perfect_t perfect_cache(sz, keys); cache::LRU_t lru{sz}; cache::LFU_t lfu{sz}; + std::map cache_hits; + for (const auto key : keys) { + if (perfect_cache.lookup_update(key, slow_get_page)) + cache_hits["perfect_cache"] += 1; - for (int i = 0; i < nelts; ++i) { - int p; - std::cin >> p; - assert(std::cin.good()); - if (lru.lookup_update(p, slow_get_page)) + if (lru.lookup_update(key, slow_get_page)) cache_hits["LRU"] += 1; - if (lfu.lookup_update(p, slow_get_page)) + + if (lfu.lookup_update(key, slow_get_page)) cache_hits["LFU"] += 1; } diff --git a/perfect/CMakeLists.txt b/perfect/CMakeLists.txt new file mode 100644 index 0000000..73a855a --- /dev/null +++ b/perfect/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(perfect INTERFACE) + +target_sources(perfect + INTERFACE + FILE_SET HEADERS + FILES + perfect.hpp +) diff --git a/perfect/perfect.hpp b/perfect/perfect.hpp new file mode 100644 index 0000000..d66df66 --- /dev/null +++ b/perfect/perfect.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace cache { + +template class perfect_t { + size_t sz_; + using KV = typename std::pair; + + using const_MapIt = typename std::unordered_map::const_iterator; + std::unordered_map hash_; + std::list future_keys_; + +public: + explicit perfect_t(size_t sz, const std::vector &fkeys) + : sz_{sz}, future_keys_(fkeys.begin(), fkeys.end()) {} + + bool full() const { return hash_.size() == sz_; }; + + template bool lookup_update(KeyT key, F slow_get_page) { + assert(future_keys_.front() == key); + future_keys_.pop_front(); + auto hit = hash_.find(key); + + if (hit != hash_.end()) + return true; + + // not found + if (full()) { + if (sz_ == 0) { + return false; + } + // eviction strategy: + // "Replace the page that will not be used for the longest time in the + // future." + auto it_to_remove = find_furthest_in_the_future(); + hash_.erase(it_to_remove); + } + hash_.emplace(key, slow_get_page(key)); + return false; + } + +private: + size_t distance_until(const KeyT &key) const { + auto it = std::find(future_keys_.begin(), future_keys_.end(), key); + return std::distance(future_keys_.begin(), it); + } + + const_MapIt find_furthest_in_the_future() const { + return std::max_element( + hash_.begin(), hash_.end(), [&](const KV &lhs, const KV &rhs) { + return distance_until(lhs.first) < distance_until(rhs.first); + }); + } +}; +} // namespace cache diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23fbb18..43d206f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,7 @@ FetchContent_MakeAvailable(googletest) add_executable(caches_test LRU_test.cpp LFU_test.cpp + perfect_test.cpp ) target_compile_options(caches_test @@ -24,6 +25,7 @@ target_link_libraries(caches_test PRIVATE LRU LFU + perfect GTest::gtest_main ) diff --git a/tests/LFU_test.cpp b/tests/LFU_test.cpp index 3ea0001..1e20f86 100644 --- a/tests/LFU_test.cpp +++ b/tests/LFU_test.cpp @@ -87,12 +87,13 @@ TEST(LFU, eviction_LRU) { size_t nhits(const std::vector &input) { size_t cache_sz = input[0]; - int nelem = input[1]; + size_t nelem = input[1]; + std::vector page_ids(input.begin() + 2, input.end()); + assert(page_ids.size() == nelem); cache::LFU_t lfu{cache_sz}; size_t hits{}; - for (int i = 0; i < nelem; ++i) { - int page_id = input[i + 2]; + for (int page_id : page_ids) { if (lfu.lookup_update(page_id, slow_get_page)) hits += 1; } @@ -108,17 +109,18 @@ TEST(LFU, example_from_lecture) { // in the vector the first element is cache size, second is legth of input, // the rest is input std::vector input_hits = { - {0, {0, 3, 1, 2, 3}}, // cache size 0 - 0 hits - {4, {2, 6, 1, 2, 1, 2, 1, 2}}, // xx1212 - 4 hits - {0, {3, 7, 1, 2, 3, 4, 5, 6, 7}}, // no repeats + {0, {0, 3, 1, 2, 3}}, // cache size 0 - 0 hits + {4, {2, 6, 1, 2, 1, 2, 1, 2}}, // xx1212 - 4 hits + {0, {3, 7, 1, 2, 3, 4, 5, 6, 7}}, // no repeats {6, {4, 12, 1, 2, 3, 4, 1, 2, 5, 1, 2, 4, 3, 4}}, // xxxx12x124x4 - 6 hits - {2, {2, 6, 1, 2, 1, 3, 1, 2}}, // xx1x1x - 2 hits - {4, {3, 9, 1, 2, 3, 1, 2, 4, 1, 2, 3}}, // xxx12x12x - 4 hits + {2, {2, 6, 1, 2, 1, 3, 1, 2}}, // xx1x1x - 2 hits + {4, {3, 9, 1, 2, 3, 1, 2, 4, 1, 2, 3}}, // xxx12x12x - 4 hits {7, {3, 12, 1, 2, 3, 1, 1, 2, 4, 1, 2, 5, 1, 2}}, // xxx112x12x12 - 7 hits {5, {4, 12, 1, 2, 3, 4, 1, 2, 1, 5, 1, 2, 3, 4}}, // xxxx121x123x - 5 hits - {7, {3, 11, 1, 2, 3, 1, 1, 2, 1, 4, 1, 2, 1}}, // xxx1121x121 - 7 hits - {3, {1, 5, 1, 1, 2, 2, 2}}, // x1x22 - 3 hits - {9, {3, 12, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}}; // xxx123123123 - 9 hits + {7, {3, 11, 1, 2, 3, 1, 1, 2, 1, 4, 1, 2, 1}}, // xxx1121x121 - 7 hits + {3, {1, 5, 1, 1, 2, 2, 2}}, // x1x22 - 3 hits + {9, {3, 12, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}}, // xxx123123123 - 9 hits + }; for (auto &[cache_hits, input_data] : input_hits) { EXPECT_EQ(nhits(input_data), cache_hits); diff --git a/tests/LRU_test.cpp b/tests/LRU_test.cpp index 17c7e89..0d12d15 100644 --- a/tests/LRU_test.cpp +++ b/tests/LRU_test.cpp @@ -67,12 +67,13 @@ TEST(LRU, eviction) { size_t nhits(const std::vector &input) { size_t cache_sz = input[0]; - int nelem = input[1]; + size_t nelem = input[1]; + std::vector page_ids(input.begin() + 2, input.end()); + assert(page_ids.size() == nelem); cache::LRU_t lru{cache_sz}; size_t hits{}; - for (int i = 0; i < nelem; ++i) { - int page_id = input[i + 2]; + for (int page_id : page_ids) { if (lru.lookup_update(page_id, slow_get_page)) hits += 1; } @@ -88,9 +89,7 @@ TEST(LRU, example_from_lecture) { std::vector input_hits = { {4, {2, 6, 1, 2, 1, 2, 1, 2}}, {0, {3, 7, 1, 2, 3, 4, 5, 6, 7}}, - {6, - {4, 12, 1, 2, 3, 4, 1, 2, 5, 1, 2, 4, 3, - 4}} // xxxx12x124x4 - 6 hits example from slides + {6, {4, 12, 1, 2, 3, 4, 1, 2, 5, 1, 2, 4, 3, 4}}, // xxxx12x124x4 - 6 hits example from slides }; for (auto &[cache_hits, input_data] : input_hits) { diff --git a/tests/perfect_test.cpp b/tests/perfect_test.cpp new file mode 100644 index 0000000..1304617 --- /dev/null +++ b/tests/perfect_test.cpp @@ -0,0 +1,128 @@ +#include "perfect.hpp" +#include +#include +#include + +namespace { +struct page_t { + int id; +}; + +page_t slow_get_page(int page_id) { return page_t{page_id}; } + +TEST(Perfect_cache, zero_space_cache) { + cache::perfect_t perf_cache{0, {1, 1}}; + EXPECT_TRUE(perf_cache.full()); + + EXPECT_FALSE(perf_cache.lookup_update(1, slow_get_page)); + EXPECT_FALSE(perf_cache.lookup_update(1, slow_get_page)); + EXPECT_TRUE(perf_cache.full()); +} + +TEST(Perfect_cache, one_hit) { + cache::perfect_t perf_cache{1, {1, 1, 1}}; + EXPECT_FALSE(perf_cache.full()); + + EXPECT_FALSE( + perf_cache.lookup_update(1, slow_get_page)); // cache is empty, no hits + EXPECT_TRUE(perf_cache.full()); + EXPECT_TRUE(perf_cache.lookup_update(1, slow_get_page)); // cache hit + EXPECT_TRUE(perf_cache.lookup_update(1, slow_get_page)); // cache hit + EXPECT_TRUE(perf_cache.full()); +} + +TEST(Perfect_cache, is_full) { + cache::perfect_t perf_cache{2, {1, 2}}; + + perf_cache.lookup_update(1, slow_get_page); + EXPECT_FALSE(perf_cache.full()); + + perf_cache.lookup_update(2, slow_get_page); + EXPECT_TRUE(perf_cache.full()); +} + +TEST(Perfect_cache, no_eviction) { + cache::perfect_t perf_cache{3, {1, 2, 1, 3, 1, 2}}; + + EXPECT_FALSE(perf_cache.lookup_update(1, slow_get_page)); + EXPECT_FALSE(perf_cache.lookup_update(2, slow_get_page)); + EXPECT_TRUE(perf_cache.lookup_update(1, slow_get_page)); + EXPECT_FALSE(perf_cache.lookup_update(3, slow_get_page)); + EXPECT_TRUE(perf_cache.lookup_update(1, slow_get_page)); + EXPECT_TRUE(perf_cache.lookup_update(2, slow_get_page)); +} + +TEST(Perfect_cache, eviction) { + // perfect cache knows the future + cache::perfect_t perf_cache{2, {1, 1, 2, 3, 2}}; + + perf_cache.lookup_update(1, slow_get_page); // [1] future: 1 2 3 2 + perf_cache.lookup_update(1, slow_get_page); // [1] future: 2 3 2 + perf_cache.lookup_update(2, slow_get_page); // [1, 2] future: 3, 2 + perf_cache.lookup_update(3, + slow_get_page); // [2, 3] no future for 1, eviction + + EXPECT_TRUE(perf_cache.lookup_update(2, slow_get_page)); +} + +TEST(Perfect_cache, eviction_2) { + // perfect cache knows the future + cache::perfect_t perf_cache{2, {1, 2, 1, 2, 3, 1, 3, 2}}; + + perf_cache.lookup_update(1, slow_get_page); // [1] future 2, 1, 2, 3, 1, 3, 2 + perf_cache.lookup_update(2, + slow_get_page); // [1,2] future 1, 2, 3, 1, 3, 2 + perf_cache.lookup_update(1, slow_get_page); // [1, 2] future 2, 3, 1, 3, 2 + perf_cache.lookup_update(2, slow_get_page); // [1, 2] future 3, 1, 3, 2 + + perf_cache.lookup_update(3, slow_get_page); // furthest in the future is 2, so + // evict it; [1, 3] future 1, 3, 2 + + EXPECT_TRUE(perf_cache.lookup_update(1, slow_get_page)); // [1, 3] future 3, 2 + EXPECT_TRUE(perf_cache.lookup_update(3, slow_get_page)); // [1, 3] future 2 + EXPECT_FALSE(perf_cache.lookup_update( + 2, slow_get_page)); // doesn't matter which evict, both has no future +} + +size_t nhits(const std::vector &input) { + size_t cache_sz = input[0]; + size_t nelem = input[1]; + std::vector page_ids(input.begin() + 2, input.end()); + assert(page_ids.size() == nelem); + + cache::perfect_t perf_cache{cache_sz, page_ids}; + size_t hits{}; + for (int page_id : page_ids) { + if (perf_cache.lookup_update(page_id, slow_get_page)) + hits += 1; + } + return hits; +} + +struct CacheHits { + size_t hit; + std::vector data; +}; + +TEST(Perfect_cache, end_to_end) { + // in the vector the first element is cache size, second is legth of input, + // the rest is input + std::vector input_hits = { + {0, {0, 3, 1, 2, 3}}, // cache size 0 - 0 hits + {4, {2, 6, 1, 2, 1, 2, 1, 2}}, // xx1212 - 4 hits + {0, {3, 7, 1, 2, 3, 4, 5, 6, 7}}, // no repeats + {6, {4, 12, 1, 2, 3, 4, 1, 2, 5, 1, 2, 4, 3, 4}}, // xxxx12x124x4 - 6 hits + {2, {2, 6, 1, 2, 1, 3, 1, 2}}, // xx1x1x - 2 hits + {4, {3, 9, 1, 2, 3, 1, 2, 4, 1, 2, 3}}, // xxx12x12x - 4 hits + {7, {3, 12, 1, 2, 3, 1, 1, 2, 4, 1, 2, 5, 1, 2}}, // xxx112x12x12 - 7 hits + {6, {4, 12, 1, 2, 3, 4, 1, 2, 1, 5, 1, 2, 3, 4}}, // xxxx121x123 - 6 hits + {7, {3, 11, 1, 2, 3, 1, 1, 2, 1, 4, 1, 2, 1}}, // xxx1121x121 - 7 hits + {3, {1, 5, 1, 1, 2, 2, 2}}, // x1x22 - 3 hits + {9, {3, 12, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}} // xxx123123123 - 9 hits + }; + + for (auto &[cache_hits, input_data] : input_hits) { + EXPECT_EQ(nhits(input_data), cache_hits); + } +} +} // namespace