diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c3131a..ecc4a9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) add_subdirectory(main) add_subdirectory(LRU) +add_subdirectory(LFU) include(CTest) diff --git a/LFU/CMakeLists.txt b/LFU/CMakeLists.txt new file mode 100644 index 0000000..04c8439 --- /dev/null +++ b/LFU/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(LFU INTERFACE) + +target_sources(LFU + INTERFACE + FILE_SET HEADERS + FILES + LFU.hpp +) diff --git a/LFU/LFU.hpp b/LFU/LFU.hpp new file mode 100644 index 0000000..2334933 --- /dev/null +++ b/LFU/LFU.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +namespace cache { + +template class LFU_t { + size_t sz_; + struct NodeInfo { + KeyT key_; + T page_; + size_t freq_; + }; + std::list cache_; + + using ListIt = typename decltype(cache_)::iterator; + std::unordered_map hash_; + +public: + explicit LFU_t(size_t sz) : sz_{sz} {} + + bool full() const { return cache_.size() == sz_; }; + + template bool lookup_update(KeyT key, F slow_get_page) { + auto hit = hash_.find(key); + if (hit == hash_.end()) { // not found + if (full()) { + if (sz_ == 0) { + return false; + } + hash_.erase(cache_.back().key_); + cache_.pop_back(); + } + auto it = find_mru_node_for_frequency(1); + it = cache_.emplace(it, NodeInfo{key, slow_get_page(key), 1}); + hash_.emplace(key, it); + return false; + } + + auto eltit = hit->second; + eltit->freq_ += 1; + auto it = find_mru_node_for_frequency(eltit->freq_); + cache_.splice(it, cache_, eltit); + return true; + } + +private: + ListIt find_mru_node_for_frequency(size_t freq) { + return std::lower_bound(cache_.begin(), cache_.end(), freq, + [](const NodeInfo &node, size_t target) { + return node.freq_ > target; + }); + } +}; + +} // namespace cache diff --git a/LRU/LRU.hpp b/LRU/LRU.hpp index 16f64cd..387af0b 100644 --- a/LRU/LRU.hpp +++ b/LRU/LRU.hpp @@ -7,7 +7,11 @@ namespace cache { template class LRU_t { size_t sz_; - std::list> cache_; + struct NodeInfo { + KeyT key_; + T page_; + }; + std::list cache_; using ListIt = typename decltype(cache_)::iterator; std::unordered_map hash_; @@ -21,7 +25,10 @@ template class LRU_t { auto hit = hash_.find(key); if (hit == hash_.end()) { // not found if (full()) { - hash_.erase(cache_.back().first); + if (sz_ == 0) { + return false; + } + hash_.erase(cache_.back().key_); cache_.pop_back(); } cache_.emplace_front(key, slow_get_page(key)); diff --git a/README.md b/README.md index 3fd263a..48cdd45 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ # cpp_proj +How to run +```bash +./build/main/main <<< "2 6 3 3 1 2 1 2" # cache_size Nelem elem1 elem2 ... +``` +output is +```bash +Cache type N hits +LRU 3 +LFU 1 +``` diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c0efbd5..bf48b14 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -13,4 +13,5 @@ target_compile_options(main target_link_libraries(main PRIVATE LRU + LFU ) diff --git a/main/main.cpp b/main/main.cpp index c7a109c..049875d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,3 +1,4 @@ +#include "LFU.hpp" #include "LRU.hpp" #include #include @@ -6,7 +7,6 @@ int slow_get_page(int key) { return key; } - int main() { std::map cache_hits; size_t sz; @@ -15,6 +15,7 @@ int main() { std::cin >> sz >> nelts; cache::LRU_t lru{sz}; + cache::LFU_t lfu{sz}; for (int i = 0; i < nelts; ++i) { int p; @@ -22,10 +23,12 @@ int main() { assert(std::cin.good()); if (lru.lookup_update(p, slow_get_page)) cache_hits["LRU"] += 1; + if (lfu.lookup_update(p, slow_get_page)) + cache_hits["LFU"] += 1; } std::cout << "Cache type\tN hits\n"; - for(auto [cache_type, nhits]: cache_hits){ + for (auto [cache_type, nhits] : cache_hits) { std::cout << cache_type << "\t" << nhits << std::endl; } return 0; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index eb26a4e..23fbb18 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,7 @@ FetchContent_MakeAvailable(googletest) add_executable(caches_test LRU_test.cpp + LFU_test.cpp ) target_compile_options(caches_test @@ -22,6 +23,7 @@ target_compile_options(caches_test target_link_libraries(caches_test PRIVATE LRU + LFU GTest::gtest_main ) diff --git a/tests/LFU_test.cpp b/tests/LFU_test.cpp new file mode 100644 index 0000000..3ea0001 --- /dev/null +++ b/tests/LFU_test.cpp @@ -0,0 +1,128 @@ +#include "LFU.hpp" +#include +#include + +namespace { +struct page_t { + int id; +}; + +page_t slow_get_page(int page_id) { return page_t{page_id}; } + +TEST(LFU, zero_space_cache) { + cache::LFU_t lfu{0}; + EXPECT_TRUE(lfu.full()); + + EXPECT_FALSE(lfu.lookup_update(1, slow_get_page)); + EXPECT_FALSE(lfu.lookup_update(1, slow_get_page)); + EXPECT_TRUE(lfu.full()); +} + +TEST(LFU, one_hit) { + cache::LFU_t lfu{1}; + EXPECT_FALSE(lfu.full()); + + EXPECT_FALSE(lfu.lookup_update(1, slow_get_page)); // cache is empty, no hits + EXPECT_TRUE(lfu.full()); + EXPECT_TRUE(lfu.lookup_update(1, slow_get_page)); // cache hit + EXPECT_TRUE(lfu.lookup_update(1, slow_get_page)); // cache hit + EXPECT_TRUE(lfu.full()); +} + +TEST(LFU, is_full) { + cache::LFU_t lfu{2}; + + lfu.lookup_update(1, slow_get_page); // [{1,1}], {page, freq} + EXPECT_FALSE(lfu.full()); + + lfu.lookup_update(2, slow_get_page); // [{1,1}, {2,1}] + EXPECT_TRUE(lfu.full()); +} + +TEST(LFU, no_eviction) { + cache::LFU_t lfu{3}; + + EXPECT_FALSE(lfu.lookup_update(1, slow_get_page)); // [{1,1}] {page, freq} + EXPECT_FALSE(lfu.lookup_update(2, slow_get_page)); // [{2,1}, {1,1}] + EXPECT_TRUE(lfu.lookup_update(1, slow_get_page)); // [{1,2}, {2,1}] + EXPECT_FALSE(lfu.lookup_update(3, slow_get_page)); // [{1,2}, {3,1}, {2,1}] + EXPECT_TRUE(lfu.lookup_update(1, slow_get_page)); // [{1,3}, {3,1}, {2,1}] + EXPECT_TRUE(lfu.lookup_update(2, slow_get_page)); // [{1,3}, {2,2}, {3,1}] +} + +TEST(LFU, eviction) { + cache::LFU_t lfu{2}; + + lfu.lookup_update(1, slow_get_page); // [{1,1}] {page, freq} + lfu.lookup_update(1, slow_get_page); // [{1,2}] + lfu.lookup_update(2, slow_get_page); // [{1,2}, {2,1}] + lfu.lookup_update(3, slow_get_page); // [{1,2}, {3,1}] // 3 evicts 2 + + EXPECT_FALSE(lfu.lookup_update( + 2, slow_get_page)); // 2 is not present before lookup, node 3 is evicted +} + +TEST(LFU, eviction_LRU) { + cache::LFU_t lfu{2}; + + lfu.lookup_update(1, slow_get_page); // [{1,1}] {page, freq} + lfu.lookup_update(2, + slow_get_page); // [{2,1}, {1,1}] // freq are the same, but + // 2 is recently used, though moves forward + lfu.lookup_update( + 1, slow_get_page); // [{1,2}, {2,1}] // 1 is moved forward for higher freq + lfu.lookup_update(2, + slow_get_page); // [{2,2}, {1,2}] // freq are the same, but + // 2 is recently used, though moves forward + + // adding new page, page 1 should be evicted + lfu.lookup_update(3, slow_get_page); // [{2,2}, {3,1}] + + EXPECT_FALSE(lfu.lookup_update( + 1, slow_get_page)); // 1 was not in LFU cache, 3 is evicted + EXPECT_FALSE(lfu.lookup_update( + 3, slow_get_page)); // 3 was not in LFU cache, 1 is evicted + EXPECT_TRUE(lfu.lookup_update(2, slow_get_page)); // 2 is still there +} + +size_t nhits(const std::vector &input) { + size_t cache_sz = input[0]; + int nelem = input[1]; + + cache::LFU_t lfu{cache_sz}; + size_t hits{}; + for (int i = 0; i < nelem; ++i) { + int page_id = input[i + 2]; + if (lfu.lookup_update(page_id, slow_get_page)) + hits += 1; + } + return hits; +} + +struct CacheHits { + size_t hit; + std::vector data; +}; + +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 + {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 + {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 + + for (auto &[cache_hits, input_data] : input_hits) { + EXPECT_EQ(nhits(input_data), cache_hits); + } +} + +} // namespace diff --git a/tests/LRU_test.cpp b/tests/LRU_test.cpp index 342bd6a..17c7e89 100644 --- a/tests/LRU_test.cpp +++ b/tests/LRU_test.cpp @@ -1,5 +1,6 @@ #include "LRU.hpp" #include +#include namespace { struct page_t { @@ -7,7 +8,15 @@ struct page_t { }; page_t slow_get_page(int page_id) { return page_t{page_id}; } -} // namespace + +TEST(LRU, zero_space_cache) { + cache::LRU_t lru{0}; + EXPECT_TRUE(lru.full()); + + EXPECT_FALSE(lru.lookup_update(1, slow_get_page)); + EXPECT_FALSE(lru.lookup_update(1, slow_get_page)); + EXPECT_TRUE(lru.full()); +} TEST(LRU, no_hit) { cache::LRU_t lru{3}; @@ -56,14 +65,36 @@ TEST(LRU, eviction) { lru.lookup_update(1, slow_get_page)); // 1 is not present before lookup } -TEST(LRU, example_from_lecture) { - cache::LRU_t lru{2}; +size_t nhits(const std::vector &input) { + size_t cache_sz = input[0]; + int nelem = input[1]; + cache::LRU_t lru{cache_sz}; size_t hits{}; - for (int page_id : {1, 2, 1, 2, 1, 2}) { + for (int i = 0; i < nelem; ++i) { + int page_id = input[i + 2]; if (lru.lookup_update(page_id, slow_get_page)) hits += 1; } + return hits; +} + +struct CacheHits { + size_t hit; + std::vector data; +}; - EXPECT_EQ(4, hits); +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 + }; + + for (auto &[cache_hits, input_data] : input_hits) { + EXPECT_EQ(nhits(input_data), cache_hits); + } } +} // namespace