From 927a678056056d900ae82da1daaa432d59364ab2 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 30 Jan 2017 10:25:17 -0800 Subject: [PATCH] Add support for sorting in LRU caches Performs an in-place merge sort --- src/fish_tests.cpp | 56 ++++++++++++++++++++++++- src/lru.h | 102 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 7 deletions(-) diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 53a8334e5..8a0040fc0 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1125,11 +1125,30 @@ static void test_escape_sequences(void) { class test_lru_t : public lru_cache_t { public: - test_lru_t() : lru_cache_t(16) {} + static constexpr size_t test_capacity = 16; + typedef std::pair value_type; - std::vector> evicted; + test_lru_t() : lru_cache_t(test_capacity) {} + + std::vector evicted; void entry_was_evicted(wcstring key, int val) { evicted.push_back({key, val}); } + + std::vector values() const { + std::vector result; + for (const auto &p : *this) { + result.push_back(p); + } + return result; + } + + std::vector ints() const { + std::vector result; + for (const auto &p : *this) { + result.push_back(p.second); + } + return result; + } }; static void test_lru(void) { @@ -1137,15 +1156,48 @@ static void test_lru(void) { test_lru_t cache; std::vector> expected_evicted; + std::vector> expected_values; int total_nodes = 20; for (int i = 0; i < total_nodes; i++) { 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}); // 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 + 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.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 ints = cache.ints(); + std::stable_sort(ints.begin(), ints.end(), comparer); + + cache.stable_sort(comparer); + std::vector new_ints = cache.ints(); + if (new_ints != ints) { + auto commajoin = [](const std::vector &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(); do_test(cache.evicted.size() == size_t(total_nodes)); } diff --git a/src/lru.h b/src/lru.h index 2f4dd9878..83b770f50 100644 --- a/src/lru.h +++ b/src/lru.h @@ -113,7 +113,72 @@ class lru_cache_t { 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 + 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(left)->value, + static_cast(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 + 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: + // Constructor // Note our linked list is always circular! 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. auto iter_inserted = this->node_map.emplace(std::move(key), lru_node_t(std::move(value))); if (!iter_inserted.second) { - // already present + // already present - so promote it + promote_node(&iter_inserted.first->second); return false; } @@ -178,6 +244,32 @@ class lru_cache_t { // Number of entries 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 + 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) { while (this->size() > 0) { evict_last_node(); @@ -186,12 +278,12 @@ class lru_cache_t { // Iterator for walking nodes, from least recently used to most. class iterator { - lru_link_t *node; + const lru_link_t *node; public: typedef std::pair value_type; - explicit iterator(lru_link_t *val) : node(val) {} + explicit iterator(const lru_link_t *val) : node(val) {} void operator++() { node = node->prev; } bool operator==(const iterator &other) { return node == other.node; } bool operator!=(const iterator &other) { return !(*this == other); } @@ -202,8 +294,8 @@ class lru_cache_t { } }; - iterator begin() { return iterator(mouth.prev); } - iterator end() { return iterator(&mouth); } + iterator begin() const { return iterator(mouth.prev); }; + iterator end() const { return iterator(&mouth); }; }; #endif