mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-13 05:28:49 +00:00
Add support for sorting in LRU caches
Performs an in-place merge sort
This commit is contained in:
parent
2565ffab25
commit
927a678056
2 changed files with 151 additions and 7 deletions
|
@ -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
102
src/lru.h
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue