Add support for sorting in LRU caches

Performs an in-place merge sort
This commit is contained in:
ridiculousfish 2017-01-30 10:25:17 -08:00
parent 2565ffab25
commit 927a678056
2 changed files with 151 additions and 7 deletions

View file

@ -1125,11 +1125,30 @@ static void test_escape_sequences(void) {
class test_lru_t : public lru_cache_t<test_lru_t, int> { class test_lru_t : public lru_cache_t<test_lru_t, int> {
public: public:
test_lru_t() : lru_cache_t<test_lru_t, int>(16) {} static constexpr size_t test_capacity = 16;
typedef std::pair<wcstring, int> value_type;
std::vector<std::pair<wcstring, int>> evicted; test_lru_t() : lru_cache_t<test_lru_t, int>(test_capacity) {}
std::vector<value_type> evicted;
void entry_was_evicted(wcstring key, int val) { evicted.push_back({key, val}); } void entry_was_evicted(wcstring key, int val) { evicted.push_back({key, val}); }
std::vector<value_type> values() const {
std::vector<value_type> result;
for (const auto &p : *this) {
result.push_back(p);
}
return result;
}
std::vector<int> ints() const {
std::vector<int> result;
for (const auto &p : *this) {
result.push_back(p.second);
}
return result;
}
}; };
static void test_lru(void) { static void test_lru(void) {
@ -1137,15 +1156,48 @@ static void test_lru(void) {
test_lru_t cache; test_lru_t cache;
std::vector<std::pair<wcstring, int>> expected_evicted; std::vector<std::pair<wcstring, int>> expected_evicted;
std::vector<std::pair<wcstring, int>> expected_values;
int total_nodes = 20; int total_nodes = 20;
for (int i = 0; i < total_nodes; i++) { for (int i = 0; i < total_nodes; i++) {
do_test(cache.size() == size_t(std::min(i, 16))); do_test(cache.size() == size_t(std::min(i, 16)));
do_test(cache.values() == expected_values);
if (i < 4) expected_evicted.push_back({to_string(i), i}); if (i < 4) expected_evicted.push_back({to_string(i), i});
// Adding the node the first time should work, and subsequent times should fail. // Adding the node the first time should work, and subsequent times should fail.
do_test(cache.insert(to_string(i), i)); do_test(cache.insert(to_string(i), i));
do_test(!cache.insert(to_string(i), i + 1)); do_test(!cache.insert(to_string(i), i + 1));
expected_values.push_back({to_string(i), i});
while (expected_values.size() > test_lru_t::test_capacity) {
expected_values.erase(expected_values.begin());
}
} }
do_test(cache.evicted == expected_evicted); do_test(cache.evicted == expected_evicted);
do_test(cache.values() == expected_values);
// Stable-sort ints in reverse order
// This a/2 check ensures that some different ints compare the same
// It also gives us a different order than we started with
auto comparer = [](int a, int b){
return a/2 > b/2;
};
std::vector<int> ints = cache.ints();
std::stable_sort(ints.begin(), ints.end(), comparer);
cache.stable_sort(comparer);
std::vector<int> new_ints = cache.ints();
if (new_ints != ints) {
auto commajoin = [](const std::vector<int> &vs) {
wcstring ret;
for (int v : vs) {
append_format(ret, L"%d,", v);
}
if (! ret.empty()) ret.pop_back();
return ret;
};
err(L"LRU stable sort failed. Expected %ls, got %ls\n", commajoin(new_ints).c_str(), commajoin(ints).c_str());
}
cache.evict_all_nodes(); cache.evict_all_nodes();
do_test(cache.evicted.size() == size_t(total_nodes)); do_test(cache.evicted.size() == size_t(total_nodes));
} }

102
src/lru.h
View file

@ -113,7 +113,72 @@ class lru_cache_t {
USE(value); USE(value);
} }
// Implementation of merge step for mergesort
// Given two singly linked lists left and right,
// and a binary func F implementing less-than, return
// the list in sorted order
template <typename F>
static lru_link_t *merge(lru_link_t *left, size_t left_len,
lru_link_t *right, size_t right_len,
const F &func) {
assert(left_len > 0 && right_len > 0);
auto popleft = [&](){
lru_link_t *ret = left;
left = left->next;
left_len--;
return ret;
};
auto popright = [&](){
lru_link_t *ret = right;
right = right->next;
right_len--;
return ret;
};
lru_link_t *head;
lru_link_t **cursor = &head;
while (left_len && right_len) {
bool goleft = ! func(static_cast<lru_node_t *>(left)->value,
static_cast<lru_node_t *>(right)->value);
*cursor = goleft ? popleft() : popright();
cursor = &(*cursor)->next;
}
while (left_len || right_len) {
*cursor = left_len ? popleft() : popright();
cursor = &(*cursor)->next;
}
return head;
}
// mergesort the given list of the given length
// This only sets the next pointers, not the prev ones
template<typename F>
static lru_link_t *mergesort(lru_link_t *node, size_t length, const F &func) {
if (length <= 1) {
return node;
}
// divide us into two lists, left and right
const size_t left_len = length / 2;
const size_t right_len = length - left_len;
lru_link_t *left = node;
lru_link_t *right = node;
for (size_t i=0; i < left_len; i++) {
right = right->next;
}
// Recursive sorting
left = mergesort(left, left_len, func);
right = mergesort(right, right_len, func);
// Merge them
return merge(left, left_len, right, right_len, func);
}
public: public:
// Constructor // Constructor
// Note our linked list is always circular! // Note our linked list is always circular!
explicit lru_cache_t(size_t max_size = 1024) : max_node_count(max_size) { explicit lru_cache_t(size_t max_size = 1024) : max_node_count(max_size) {
@ -159,7 +224,8 @@ class lru_cache_t {
// Try inserting; return false if it was already in the set. // Try inserting; return false if it was already in the set.
auto iter_inserted = this->node_map.emplace(std::move(key), lru_node_t(std::move(value))); auto iter_inserted = this->node_map.emplace(std::move(key), lru_node_t(std::move(value)));
if (!iter_inserted.second) { if (!iter_inserted.second) {
// already present // already present - so promote it
promote_node(&iter_inserted.first->second);
return false; return false;
} }
@ -178,6 +244,32 @@ class lru_cache_t {
// Number of entries // Number of entries
size_t size() { return this->node_map.size(); } size_t size() { return this->node_map.size(); }
// Sorting support
// Given a binary function F implementing less-than on the contents, place the nodes in sorted order
template<typename F>
void stable_sort(const F &func) {
// Perform the sort. This sets forward pointers only
size_t length = this->size();
if (length <= 1) {
return;
}
lru_link_t *sorted = mergesort(this->mouth.next, length, func);
mouth.next = sorted;
// Go through and set back back pointers
lru_link_t *cursor = sorted;
lru_link_t *prev = &mouth;
for (size_t i=0; i < length; i++) {
cursor->prev = prev;
prev = cursor;
cursor = cursor->next;
}
// prev is now last element in list
// make the list circular
prev->next = &mouth;
mouth.prev = prev;
}
void evict_all_nodes(void) { void evict_all_nodes(void) {
while (this->size() > 0) { while (this->size() > 0) {
evict_last_node(); evict_last_node();
@ -186,12 +278,12 @@ class lru_cache_t {
// Iterator for walking nodes, from least recently used to most. // Iterator for walking nodes, from least recently used to most.
class iterator { class iterator {
lru_link_t *node; const lru_link_t *node;
public: public:
typedef std::pair<const wcstring &, const CONTENTS &> value_type; typedef std::pair<const wcstring &, const CONTENTS &> value_type;
explicit iterator(lru_link_t *val) : node(val) {} explicit iterator(const lru_link_t *val) : node(val) {}
void operator++() { node = node->prev; } void operator++() { node = node->prev; }
bool operator==(const iterator &other) { return node == other.node; } bool operator==(const iterator &other) { return node == other.node; }
bool operator!=(const iterator &other) { return !(*this == other); } bool operator!=(const iterator &other) { return !(*this == other); }
@ -202,8 +294,8 @@ class lru_cache_t {
} }
}; };
iterator begin() { return iterator(mouth.prev); } iterator begin() const { return iterator(mouth.prev); };
iterator end() { return iterator(&mouth); } iterator end() const { return iterator(&mouth); };
}; };
#endif #endif