From 97f8d913e24200fd149154dd5246fb18fc485d4d Mon Sep 17 00:00:00 2001 From: Joe Thornber Date: Fri, 21 Oct 2011 16:57:28 +0100 Subject: [PATCH] first pass at cache --- cache.h | 253 +++++++++++++++++++++++++++++++++++++++++ unit-tests/Makefile.in | 4 + unit-tests/block_t.cc | 2 +- unit-tests/cache_t.cc | 144 +++++++++++++++++++++++ 4 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 cache.h create mode 100644 unit-tests/cache_t.cc diff --git a/cache.h b/cache.h new file mode 100644 index 0000000..12b5e6e --- /dev/null +++ b/cache.h @@ -0,0 +1,253 @@ +#ifndef CACHE_H +#define CACHE_H + +#include +#include +#include +#include +#include +#include + +//---------------------------------------------------------------- + +namespace base { + // FIXME: move somewhere more useful + template + struct deleter { + void operator()(T *t) { + delete t; + } + }; + + // ValueTraits needs to define value_type, key_type and a get_key() + // static function. Commonly you will want value_type to be a + // shared_ptr, with any teardown specific stuff in the destructor. + + template + class cache { + public: + typedef typename ValueTraits::value_type value_type; + typedef typename ValueTraits::key_type key_type; + + cache(unsigned max_entries); + ~cache(); + + void insert(value_type const &v); + + boost::optional get(key_type const &k); + void put(value_type const &k); + + private: + void make_space(); + + struct value_entry { + // FIXME: this means the cached object must have a + // default constructor also, which is a shame. + // so we can construct the headers. + value_entry() + : ref_count_(0) { + } + + explicit value_entry(value_type v) + : ref_count_(0), + v_(v) { + } + + struct { + value_entry *next_, *prev_; + } lru_; + + struct { + value_entry *parent_, *left_, *right_; + int color_; + } lookup_; + + unsigned ref_count_; + value_type v_; + }; + + struct value_ptr_cmp { + bool operator() (value_entry const *lhs, value_entry const *rhs) { + key_type k1 = ValueTraits::get_key(lhs->v_); + key_type k2 = ValueTraits::get_key(rhs->v_); + + return k1 < k2; + } + }; + + struct key_value_ptr_cmp { + bool operator() (key_type const &k1, value_entry const *rhs) { + key_type k2 = ValueTraits::get_key(rhs->v_); + return k1 < k2; + } + + bool operator() (value_entry const *lhs, key_type const &k2) { + key_type k1 = ValueTraits::get_key(lhs->v_); + return k1 < k2; + } + + }; + + struct list_node_traits { + typedef value_entry node; + typedef value_entry *node_ptr; + typedef const value_entry *const_node_ptr; + + static node_ptr get_next(const_node_ptr n) { + return n->lru_.next_; + } + + static void set_next(node_ptr n, node_ptr next) { + n->lru_.next_ = next; + } + + static node_ptr get_previous(const_node_ptr n) { + return n->lru_.prev_; + } + + static void set_previous(node_ptr n, node_ptr prev) { + n->lru_.prev_ = prev; + } + }; + + struct rbtree_node_traits { + typedef value_entry node; + typedef value_entry *node_ptr; + typedef const value_entry * const_node_ptr; + typedef int color; + + static node_ptr get_parent(const_node_ptr n) { + return n->lookup_.parent_; + } + + static void set_parent(node_ptr n, node_ptr parent) { + n->lookup_.parent_ = parent; + } + + static node_ptr get_left(const_node_ptr n) { + return n->lookup_.left_; + } + + static void set_left(node_ptr n, node_ptr left) { + n->lookup_.left_ = left; + } + + static node_ptr get_right(const_node_ptr n) { + return n->lookup_.right_; + } + + static void set_right(node_ptr n, node_ptr right) { + n->lookup_.right_ = right; + } + + static int get_color(const_node_ptr n) { + return n->lookup_.color_; + } + + static void set_color(node_ptr n, color c) { + n->lookup_.color_ = c; + } + + static color red() { + return 0; + } + + static color black() { + return 1; + } + }; + + typedef boost::intrusive::circular_list_algorithms lru_algo; + typedef boost::intrusive::rbtree_algorithms lookup_algo; + + unsigned max_entries_; + unsigned current_entries_; + + value_entry lru_header_; + value_entry lookup_header_; + }; + + template + cache::cache(unsigned max_entries) + : max_entries_(max_entries), + current_entries_(0) { + lru_algo::init_header(&lru_header_); + lookup_algo::init_header(&lookup_header_); + } + + template + cache::~cache() { + deleter d; + lookup_algo::clear_and_dispose(&lookup_header_, d); + } + + template + void + cache::insert(value_type const &v) { + make_space(); + + value_entry *node = new value_entry(v); + try { + lru_algo::link_after(&lru_header_, node); + try { + value_ptr_cmp cmp; + lookup_algo::insert_equal(&lookup_header_, &lookup_header_, node, cmp); + current_entries_++; + + } catch (...) { + lru_algo::unlink(node); + throw; + } + + } catch (...) { + delete node; + throw; + } + } + + template + boost::optional + cache::get(key_type const &k) { + key_value_ptr_cmp cmp; + value_entry *node = lookup_algo::find(&lookup_header_, k, cmp); + if (node == &lookup_header_) + return boost::optional(); + + if (!node->ref_count_++) + lru_algo::unlink(node); + return boost::optional(node->v_); + } + + template + void + cache::put(value_type const &v) { + // FIXME: the lookup will go once we use a proper hook + key_value_ptr_cmp cmp; + key_type k = ValueTraits::get_key(v); + value_entry *node = lookup_algo::find(&lookup_header_, k, cmp); + if (node == &lookup_header_) + throw std::runtime_error("invalid put"); + + if (!--node->ref_count_) + lru_algo::link_after(&lru_header_, node); + } + + template + void + cache::make_space() { + if (current_entries_ == max_entries_) { + value_entry *node = lru_header_.lru_.prev_; + if (node == &lru_header_) + throw std::runtime_error("cache full"); + + lru_algo::unlink(node); + lookup_algo::unlink(node); + delete node; + current_entries_--; + } + } +} + +//---------------------------------------------------------------- + +#endif diff --git a/unit-tests/Makefile.in b/unit-tests/Makefile.in index 5ca5fd9..f485e30 100644 --- a/unit-tests/Makefile.in +++ b/unit-tests/Makefile.in @@ -1,6 +1,7 @@ TEST_SOURCE=\ unit-tests/block_t.cc \ unit-tests/btree_t.cc \ + unit-tests/cache_t.cc \ unit-tests/endian_t.cc \ unit-tests/space_map_t.cc \ unit-tests/space_map_disk_t.cc \ @@ -19,6 +20,9 @@ unit-tests/block_t: unit-tests/block_t.o unit-tests/btree_t: unit-tests/btree_t.o $(OBJECTS) g++ $(CPPFLAGS) -o $@ $+ $(LIBS) +unit-tests/cache_t: unit-tests/cache_t.o $(OBJECTS) + g++ $(CPPFLAGS) -o $@ $+ $(LIBS) + unit-tests/space_map_t: unit-tests/space_map_t.o $(OBJECTS) g++ $(CPPFLAGS) -o $@ $+ $(LIBS) diff --git a/unit-tests/block_t.cc b/unit-tests/block_t.cc index 654c937..b00e617 100644 --- a/unit-tests/block_t.cc +++ b/unit-tests/block_t.cc @@ -24,7 +24,7 @@ namespace { void check(block_manager<4096>::block const &blk) const { for (unsigned b = 0; b < BlockSize; b++) if (blk.data_[b] != 0) - throw runtime_error("validator check zero"); + throw runtime_error("validator check zero"); } void prepare(block_manager<4096>::block &blk) const { diff --git a/unit-tests/cache_t.cc b/unit-tests/cache_t.cc new file mode 100644 index 0000000..e42bf1d --- /dev/null +++ b/unit-tests/cache_t.cc @@ -0,0 +1,144 @@ +#include "cache.h" + +#define BOOST_TEST_MODULE CacheTests +#include + +#include + +using namespace boost; +using namespace base; +using namespace std; + +//---------------------------------------------------------------- + +namespace { + struct Thing { + Thing() + : key_(0), + desc_("default constructed") { + } + + Thing(unsigned n) + : key_(n), + desc_("foo bar") { + } + + unsigned key_; + string desc_; + }; + + struct ThingTraits { + typedef unsigned key_type; + typedef Thing value_type; + + static key_type get_key(value_type const &t) { + return t.key_; + } + }; + + struct SharedThingTraits { + typedef unsigned key_type; + typedef shared_ptr value_type; + + static key_type get_key(value_type const &p) { + return p->key_; + } + }; +} + +//---------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(cache_creation) +{ + cache c(16); +} + +BOOST_AUTO_TEST_CASE(cache_caches) +{ + unsigned const COUNT = 16; + cache c(COUNT); + + for (unsigned i = 0; i < COUNT; i++) + c.insert(Thing(i)); + + for (unsigned i = 0; i < COUNT; i++) + BOOST_ASSERT(c.get(i)); +} + +BOOST_AUTO_TEST_CASE(cache_drops_elements) +{ + unsigned const COUNT = 1024; + unsigned const CACHE_SIZE = 16; + cache c(CACHE_SIZE); + + for (unsigned i = 0; i < COUNT; i++) + c.insert(Thing(i)); + + for (unsigned i = 0; i < COUNT - CACHE_SIZE; i++) + BOOST_ASSERT(!c.get(i)); + + for (unsigned i = COUNT - CACHE_SIZE; i < COUNT; i++) + BOOST_ASSERT(c.get(i)); +} + +BOOST_AUTO_TEST_CASE(held_entries_count_towards_the_cache_limit) +{ + unsigned const CACHE_SIZE = 16; + cache c(CACHE_SIZE); + + unsigned i; + for (i = 0; i < CACHE_SIZE; i++) { + c.insert(Thing(i)); + c.get(i); + } + + BOOST_CHECK_THROW(c.insert(Thing(i)), runtime_error); +} + +BOOST_AUTO_TEST_CASE(put_works) +{ + unsigned const CACHE_SIZE = 16; + cache c(CACHE_SIZE); + + unsigned i; + for (i = 0; i < CACHE_SIZE; i++) { + c.insert(Thing(i)); + c.get(i); + c.put(Thing(i)); + } + + // should succeed + c.insert(Thing(i)); +} + +BOOST_AUTO_TEST_CASE(multiple_gets_works) +{ + unsigned const CACHE_SIZE = 16; + cache c(CACHE_SIZE); + + unsigned i; + for (i = 0; i < CACHE_SIZE; i++) { + c.insert(Thing(i)); + c.get(i); + c.get(i); + c.put(Thing(i)); + } + + BOOST_CHECK_THROW(c.insert(Thing(i)), runtime_error); +} + +BOOST_AUTO_TEST_CASE(shared_ptr_cache_works) +{ + unsigned const CACHE_SIZE = 16; + cache c(CACHE_SIZE); + + for (unsigned i = 0; i < CACHE_SIZE; i++) { + c.insert(shared_ptr(new Thing(i))); + optional > maybe_ptr = c.get(i); + + BOOST_ASSERT(maybe_ptr); + BOOST_ASSERT((*maybe_ptr)->key_ == i); + } +} + +//----------------------------------------------------------------