Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)

add_subdirectory(main)
add_subdirectory(LRU)
add_subdirectory(LFU)


include(CTest)
Expand Down
8 changes: 8 additions & 0 deletions LFU/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
add_library(LFU INTERFACE)

target_sources(LFU
INTERFACE
FILE_SET HEADERS
FILES
LFU.hpp
)
58 changes: 58 additions & 0 deletions LFU/LFU.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <algorithm>
#include <list>
#include <unordered_map>

namespace cache {

template <typename T, typename KeyT = int> class LFU_t {
size_t sz_;
struct NodeInfo {
KeyT key_;
T page_;
size_t freq_;
};
std::list<NodeInfo> cache_;

using ListIt = typename decltype(cache_)::iterator;
std::unordered_map<KeyT, ListIt> hash_;

public:
explicit LFU_t(size_t sz) : sz_{sz} {}

bool full() const { return cache_.size() == sz_; };

template <typename F> 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
11 changes: 9 additions & 2 deletions LRU/LRU.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ namespace cache {

template <typename T, typename KeyT = int> class LRU_t {
size_t sz_;
std::list<std::pair<KeyT, T>> cache_;
struct NodeInfo {
KeyT key_;
T page_;
};
std::list<NodeInfo> cache_;

using ListIt = typename decltype(cache_)::iterator;
std::unordered_map<KeyT, ListIt> hash_;
Expand All @@ -21,7 +25,10 @@ template <typename T, typename KeyT = int> 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));
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
```



Expand Down
1 change: 1 addition & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ target_compile_options(main
target_link_libraries(main
PRIVATE
LRU
LFU
)
7 changes: 5 additions & 2 deletions main/main.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "LFU.hpp"
#include "LRU.hpp"
#include <cassert>
#include <iostream>
Expand All @@ -6,7 +7,6 @@

int slow_get_page(int key) { return key; }


int main() {
std::map<std::string, int> cache_hits;
size_t sz;
Expand All @@ -15,17 +15,20 @@ int main() {
std::cin >> sz >> nelts;

cache::LRU_t<int> lru{sz};
cache::LFU_t<int> lfu{sz};

for (int i = 0; i < nelts; ++i) {
int p;
std::cin >> p;
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;
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ FetchContent_MakeAvailable(googletest)

add_executable(caches_test
LRU_test.cpp
LFU_test.cpp
)

target_compile_options(caches_test
Expand All @@ -22,6 +23,7 @@ target_compile_options(caches_test
target_link_libraries(caches_test
PRIVATE
LRU
LFU
GTest::gtest_main
)

Expand Down
128 changes: 128 additions & 0 deletions tests/LFU_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "LFU.hpp"
#include <gtest/gtest.h>
#include <vector>

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<page_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<page_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<page_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<page_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<page_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<page_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<int> &input) {
size_t cache_sz = input[0];
int nelem = input[1];

cache::LFU_t<page_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<int> 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<CacheHits> 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
41 changes: 36 additions & 5 deletions tests/LRU_test.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
#include "LRU.hpp"
#include <gtest/gtest.h>
#include <vector>

namespace {
struct page_t {
int id;
};

page_t slow_get_page(int page_id) { return page_t{page_id}; }
} // namespace

TEST(LRU, zero_space_cache) {
cache::LRU_t<page_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<page_t> lru{3};
Expand Down Expand Up @@ -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<page_t> lru{2};
size_t nhits(const std::vector<int> &input) {
size_t cache_sz = input[0];
int nelem = input[1];

cache::LRU_t<page_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<int> data;
};

EXPECT_EQ(4, hits);
TEST(LRU, example_from_lecture) {
std::vector<CacheHits> 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
Loading