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
2 changes: 1 addition & 1 deletion .github/workflows/cmake-multi-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
add_subdirectory(main)
add_subdirectory(LRU)
add_subdirectory(LFU)
add_subdirectory(perfect)


include(CTest)
Expand Down
1 change: 1 addition & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ target_link_libraries(main
PRIVATE
LRU
LFU
perfect
)
1 change: 1 addition & 0 deletions main/input_data.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2 6 3 3 1 2 1 2
23 changes: 16 additions & 7 deletions main/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "LFU.hpp"
#include "LRU.hpp"
#include "perfect.hpp"
#include <cassert>
#include <iostream>
#include <map>
Expand All @@ -8,22 +9,30 @@
int slow_get_page(int key) { return key; }

int main() {
std::map<std::string, int> cache_hits;
size_t sz;
int nelts;

std::cin >> sz >> nelts;
std::vector<int> keys;
for (int i = 0; i < nelts; ++i) {
int key;
std::cin >> key;
assert(std::cin.good());
keys.push_back(key);
}

cache::perfect_t<int> perfect_cache(sz, keys);
cache::LRU_t<int> lru{sz};
cache::LFU_t<int> lfu{sz};
std::map<std::string, int> 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;
}

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

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

#include <algorithm>
#include <cassert>
#include <list>
#include <unordered_map>
#include <utility>
#include <vector>

namespace cache {

template <typename T, typename KeyT = int> class perfect_t {
size_t sz_;
using KV = typename std::pair<KeyT, T>;

using const_MapIt = typename std::unordered_map<KeyT, T>::const_iterator;
std::unordered_map<KeyT, T> hash_;
std::list<KeyT> future_keys_;

public:
explicit perfect_t(size_t sz, const std::vector<KeyT> &fkeys)
: sz_{sz}, future_keys_(fkeys.begin(), fkeys.end()) {}

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

template <typename F> 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
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,6 +25,7 @@ target_link_libraries(caches_test
PRIVATE
LRU
LFU
perfect
GTest::gtest_main
)

Expand Down
24 changes: 13 additions & 11 deletions tests/LFU_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ TEST(LFU, eviction_LRU) {

size_t nhits(const std::vector<int> &input) {
size_t cache_sz = input[0];
int nelem = input[1];
size_t nelem = input[1];
std::vector<int> page_ids(input.begin() + 2, input.end());
assert(page_ids.size() == nelem);

cache::LFU_t<page_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;
}
Expand All @@ -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<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
{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);
Expand Down
11 changes: 5 additions & 6 deletions tests/LRU_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@ TEST(LRU, eviction) {

size_t nhits(const std::vector<int> &input) {
size_t cache_sz = input[0];
int nelem = input[1];
size_t nelem = input[1];
std::vector<int> page_ids(input.begin() + 2, input.end());
assert(page_ids.size() == nelem);

cache::LRU_t<page_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;
}
Expand All @@ -88,9 +89,7 @@ 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
{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) {
Expand Down
128 changes: 128 additions & 0 deletions tests/perfect_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "perfect.hpp"
#include <cassert>
#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(Perfect_cache, zero_space_cache) {
cache::perfect_t<page_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<page_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<page_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<page_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<page_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<page_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<int> &input) {
size_t cache_sz = input[0];
size_t nelem = input[1];
std::vector<int> page_ids(input.begin() + 2, input.end());
assert(page_ids.size() == nelem);

cache::perfect_t<page_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<int> 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<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
{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
Loading