2016-05-03 02:54:01 +08:00
|
|
|
// Least-recently-used cache implementation.
|
2012-02-06 12:54:41 +08:00
|
|
|
#ifndef FISH_LRU_H
|
|
|
|
#define FISH_LRU_H
|
|
|
|
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <assert.h>
|
2012-02-06 12:54:41 +08:00
|
|
|
#include <wchar.h>
|
2016-05-03 02:54:01 +08:00
|
|
|
#include <list>
|
2012-02-06 12:54:41 +08:00
|
|
|
#include <map>
|
|
|
|
#include <set>
|
2016-04-21 14:00:54 +08:00
|
|
|
|
2012-02-06 12:54:41 +08:00
|
|
|
#include "common.h"
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// A predicate to compare dereferenced pointers.
|
|
|
|
struct dereference_less_t {
|
2012-02-06 12:54:41 +08:00
|
|
|
template <typename ptr_t>
|
2016-05-03 02:54:01 +08:00
|
|
|
bool operator()(ptr_t p1, ptr_t p2) const {
|
2012-11-19 08:30:30 +08:00
|
|
|
return *p1 < *p2;
|
|
|
|
}
|
2012-02-06 12:54:41 +08:00
|
|
|
};
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
class lru_node_t {
|
|
|
|
template <class T>
|
|
|
|
friend class lru_cache_t;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Our linked list pointer.
|
2012-02-06 12:54:41 +08:00
|
|
|
lru_node_t *prev, *next;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
public:
|
|
|
|
/// The key used to look up in the cache.
|
2012-02-06 12:54:41 +08:00
|
|
|
const wcstring key;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Constructor.
|
|
|
|
explicit lru_node_t(const wcstring &pkey) : prev(NULL), next(NULL), key(pkey) {}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Virtual destructor that does nothing for classes that inherit lru_node_t.
|
2014-09-24 22:37:32 +08:00
|
|
|
virtual ~lru_node_t() {}
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// operator< for std::set
|
|
|
|
bool operator<(const lru_node_t &other) const { return key < other.key; }
|
2012-02-06 12:54:41 +08:00
|
|
|
};
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
template <class node_type_t>
|
|
|
|
class lru_cache_t {
|
|
|
|
private:
|
|
|
|
/// Max node count. This may be (transiently) exceeded by add_node_without_eviction, which is
|
|
|
|
/// used from background threads.
|
2012-02-06 12:54:41 +08:00
|
|
|
const size_t max_node_count;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Count of nodes.
|
2012-02-06 12:54:41 +08:00
|
|
|
size_t node_count;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// The set of nodes.
|
2012-02-06 12:54:41 +08:00
|
|
|
typedef std::set<lru_node_t *, dereference_less_t> node_set_t;
|
|
|
|
node_set_t node_set;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
void promote_node(node_type_t *node) {
|
|
|
|
// We should never promote the mouth.
|
2012-02-06 12:54:41 +08:00
|
|
|
assert(node != &mouth);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// First unhook us.
|
2012-02-06 12:54:41 +08:00
|
|
|
node->prev->next = node->next;
|
|
|
|
node->next->prev = node->prev;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Put us after the mouth.
|
2012-02-06 12:54:41 +08:00
|
|
|
node->next = mouth.next;
|
|
|
|
node->next->prev = node;
|
|
|
|
node->prev = &mouth;
|
|
|
|
mouth.next = node;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
void evict_node(node_type_t *condemned_node) {
|
|
|
|
// We should never evict the mouth.
|
2012-02-06 12:54:41 +08:00
|
|
|
assert(condemned_node != NULL && condemned_node != &mouth);
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Remove it from the linked list.
|
2012-02-06 12:54:41 +08:00
|
|
|
condemned_node->prev->next = condemned_node->next;
|
|
|
|
condemned_node->next->prev = condemned_node->prev;
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Remove us from the set.
|
2012-02-06 12:54:41 +08:00
|
|
|
node_set.erase(condemned_node);
|
|
|
|
node_count--;
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Tell ourselves.
|
2012-02-06 12:54:41 +08:00
|
|
|
this->node_was_evicted(condemned_node);
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
void evict_last_node(void) { evict_node((node_type_t *)mouth.prev); }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
static lru_node_t *get_previous(lru_node_t *node) { return node->prev; }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
protected:
|
|
|
|
/// Head of the linked list.
|
2012-02-06 12:54:41 +08:00
|
|
|
lru_node_t mouth;
|
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Overridable callback for when a node is evicted.
|
|
|
|
virtual void node_was_evicted(node_type_t *node) {}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
public:
|
|
|
|
/// Constructor
|
|
|
|
explicit lru_cache_t(size_t max_size = 1024)
|
|
|
|
: max_node_count(max_size), node_count(0), mouth(wcstring()) {
|
|
|
|
// Hook up the mouth to itself: a one node circularly linked list!
|
2012-02-06 12:54:41 +08:00
|
|
|
mouth.prev = mouth.next = &mouth;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Note that we do not evict nodes in our destructor (even though they typically need to be
|
|
|
|
/// deleted by their creator).
|
|
|
|
virtual ~lru_cache_t() {}
|
2012-02-18 06:54:58 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Returns the node for a given key, or NULL.
|
|
|
|
node_type_t *get_node(const wcstring &key) {
|
2012-02-06 12:54:41 +08:00
|
|
|
node_type_t *result = NULL;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Construct a fake node as our key.
|
2012-02-06 12:54:41 +08:00
|
|
|
lru_node_t node_key(key);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Look for it in the set.
|
2012-02-06 12:54:41 +08:00
|
|
|
node_set_t::iterator iter = node_set.find(&node_key);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// If we found a node, promote and return it.
|
|
|
|
if (iter != node_set.end()) {
|
|
|
|
result = static_cast<node_type_t *>(*iter);
|
2012-02-06 12:54:41 +08:00
|
|
|
promote_node(result);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Evicts the node for a given key, returning true if a node was evicted.
|
|
|
|
bool evict_node(const wcstring &key) {
|
|
|
|
// Construct a fake node as our key.
|
2012-02-06 12:54:41 +08:00
|
|
|
lru_node_t node_key(key);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Look for it in the set.
|
2012-02-06 12:54:41 +08:00
|
|
|
node_set_t::iterator iter = node_set.find(&node_key);
|
2016-05-03 02:54:01 +08:00
|
|
|
if (iter == node_set.end()) return false;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Evict the given node.
|
|
|
|
evict_node(static_cast<node_type_t *>(*iter));
|
2012-02-06 12:54:41 +08:00
|
|
|
return true;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Adds a node under the given key. Returns true if the node was added, false if the node was
|
|
|
|
/// not because a node with that key is already in the set.
|
|
|
|
bool add_node(node_type_t *node) {
|
|
|
|
// Add our node without eviction.
|
|
|
|
if (!this->add_node_without_eviction(node)) return false;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
while (node_count > max_node_count) evict_last_node(); // evict
|
2012-02-06 12:54:41 +08:00
|
|
|
return true;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Adds a node under the given key without triggering eviction. Returns true if the node was
|
|
|
|
/// added, false if the node was not because a node with that key is already in the set.
|
|
|
|
bool add_node_without_eviction(node_type_t *node) {
|
2012-02-06 12:54:41 +08:00
|
|
|
assert(node != NULL && node != &mouth);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Try inserting; return false if it was already in the set.
|
|
|
|
if (!node_set.insert(node).second) return false;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Add the node after the mouth.
|
2012-02-06 12:54:41 +08:00
|
|
|
node->next = mouth.next;
|
|
|
|
node->next->prev = node;
|
|
|
|
node->prev = &mouth;
|
|
|
|
mouth.next = node;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
// Update the count. This may push us over the maximum node count.
|
2012-02-06 12:54:41 +08:00
|
|
|
node_count++;
|
|
|
|
return true;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Counts nodes.
|
|
|
|
size_t size(void) { return node_count; }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Evicts all nodes.
|
|
|
|
void evict_all_nodes(void) {
|
|
|
|
while (node_count > 0) {
|
2012-02-06 12:54:41 +08:00
|
|
|
evict_last_node();
|
|
|
|
}
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
/// Iterator for walking nodes, from least recently used to most.
|
|
|
|
class iterator {
|
2012-02-06 12:54:41 +08:00
|
|
|
lru_node_t *node;
|
2016-05-03 02:54:01 +08:00
|
|
|
|
|
|
|
public:
|
|
|
|
explicit iterator(lru_node_t *val) : node(val) {}
|
|
|
|
void operator++() { node = lru_cache_t::get_previous(node); }
|
|
|
|
void operator++(int x) { node = lru_cache_t::get_previous(node); }
|
|
|
|
bool operator==(const iterator &other) { return node == other.node; }
|
|
|
|
bool operator!=(const iterator &other) { return !(*this == other); }
|
|
|
|
node_type_t *operator*() { return static_cast<node_type_t *>(node); }
|
2012-02-06 12:54:41 +08:00
|
|
|
};
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-03 02:54:01 +08:00
|
|
|
iterator begin() { return iterator(mouth.prev); }
|
|
|
|
iterator end() { return iterator(&mouth); }
|
2012-02-06 12:54:41 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|